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/sbus/Makefile b/drivers/sbus/Makefile
new file mode 100644
index 0000000..7b1d24d9
--- /dev/null
+++ b/drivers/sbus/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for the linux kernel.
+#
+
+ifneq ($(ARCH),m68k)
+obj-y    := sbus.o dvma.o
+endif
+
+obj-$(CONFIG_SBUSCHAR) += char/
diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig
new file mode 100644
index 0000000..90d8ef1
--- /dev/null
+++ b/drivers/sbus/char/Kconfig
@@ -0,0 +1,93 @@
+
+menu "Misc Linux/SPARC drivers"
+
+config SUN_OPENPROMIO
+	tristate "/dev/openprom device support"
+	help
+	  This driver provides user programs with an interface to the SPARC
+	  PROM device tree. The driver implements a SunOS-compatible
+	  interface and a NetBSD-compatible interface.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called openprom.
+
+	  If unsure, say Y.
+
+config SUN_MOSTEK_RTC
+	tristate "Mostek real time clock support"
+	help
+	  The Mostek RTC chip is used on all known Sun computers except
+	  some JavaStations. For a JavaStation you need to say Y both here
+	  and to "Enhanced Real Time Clock Support".
+
+	  Say Y here unless you are building a special purpose kernel.
+
+config OBP_FLASH
+	tristate "OBP Flash Device support"
+	depends on SPARC64
+	help
+	  The OpenBoot PROM on Ultra systems is flashable. If you want to be
+	  able to upgrade the OBP firmware, say Y here.
+
+config SUN_BPP
+	tristate "Bidirectional parallel port support (OBSOLETE)"
+	depends on EXPERIMENTAL
+	help
+	  Say Y here to support Sun's obsolete variant of IEEE1284
+	  bidirectional parallel port protocol as /dev/bppX.  Can be built on
+	  x86 machines.
+
+config SUN_VIDEOPIX
+	tristate "Videopix Frame Grabber (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && (BROKEN || !64BIT)
+	help
+	  Say Y here to support the Videopix Frame Grabber from Sun
+	  Microsystems, commonly found on SPARCstations.  This card, which is
+	  based on the Phillips SAA9051, can handle NTSC and PAL/SECAM and
+	  SVIDEO signals.
+
+config SUN_AURORA
+	tristate "Aurora Multiboard 1600se (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && BROKEN
+	help
+	  The Aurora Multiboard is a multi-port high-speed serial controller.
+	  If you have one of these, say Y.
+
+config TADPOLE_TS102_UCTRL
+	tristate "Tadpole TS102 Microcontroller support (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && SPARC32
+	help
+	  Say Y here to directly support the TS102 Microcontroller interface
+	  on the Tadpole Sparcbook 3.  This device handles power-management
+	  events, and can also notice the attachment/detachment of external
+	  monitors and mice.
+
+config SUN_JSFLASH
+	tristate "JavaStation OS Flash SIMM (EXPERIMENTAL)"
+	depends on EXPERIMENTAL && SPARC32
+	help
+	  If you say Y here, you will be able to boot from your JavaStation's
+	  Flash memory.
+
+# XXX Why don't we do "source drivers/char/Config.in" somewhere?
+# no shit
+config APM_RTC_IS_GMT
+	bool
+	depends on EXPERIMENTAL && SPARC32 && PCI
+	default y
+	help
+	  Say Y here if your RTC (Real Time Clock a.k.a. hardware clock)
+	  stores the time in GMT (Greenwich Mean Time). Say N if your RTC
+	  stores localtime.
+
+	  It is in fact recommended to store GMT in your RTC, because then you
+	  don't have to worry about daylight savings time changes. The only
+	  reason not to use GMT in your RTC is if you also run a broken OS
+	  that doesn't understand GMT.
+
+config RTC
+	tristate "PC-style Real Time Clock Support"
+	depends on PCI && EXPERIMENTAL && SPARC32
+
+endmenu
+
diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile
new file mode 100644
index 0000000..3a5ea1d
--- /dev/null
+++ b/drivers/sbus/char/Makefile
@@ -0,0 +1,25 @@
+#
+# Makefile for the kernel miscellaneous SPARC device drivers.
+#
+# Dave Redman Frame Buffer tuning support.
+#
+# 7 October 2000, Bartlomiej Zolnierkiewicz <bkz@linux-ide.org>
+# Rewritten to use lists instead of if-statements.
+#
+
+vfc-objs := vfc_dev.o vfc_i2c.o
+bbc-objs := bbc_i2c.o bbc_envctrl.o
+
+obj-$(CONFIG_ENVCTRL)			+= envctrl.o
+obj-$(CONFIG_DISPLAY7SEG)		+= display7seg.o
+obj-$(CONFIG_WATCHDOG_CP1XXX)		+= cpwatchdog.o
+obj-$(CONFIG_WATCHDOG_RIO)		+= riowatchdog.o
+obj-$(CONFIG_OBP_FLASH)			+= flash.o
+obj-$(CONFIG_SUN_OPENPROMIO)		+= openprom.o
+obj-$(CONFIG_SUN_MOSTEK_RTC)		+= rtc.o
+obj-$(CONFIG_SUN_BPP)			+= bpp.o
+obj-$(CONFIG_SUN_VIDEOPIX)		+= vfc.o
+obj-$(CONFIG_SUN_AURORA)		+= aurora.o
+obj-$(CONFIG_TADPOLE_TS102_UCTRL)	+= uctrl.o
+obj-$(CONFIG_SUN_JSFLASH)		+= jsflash.o
+obj-$(CONFIG_BBC_I2C)			+= bbc.o
diff --git a/drivers/sbus/char/aurora.c b/drivers/sbus/char/aurora.c
new file mode 100644
index 0000000..e5fa170
--- /dev/null
+++ b/drivers/sbus/char/aurora.c
@@ -0,0 +1,2372 @@
+/*	$Id: aurora.c,v 1.19 2002/01/08 16:00:16 davem Exp $
+ *	linux/drivers/sbus/char/aurora.c -- Aurora multiport driver
+ *
+ *	Copyright (c) 1999 by Oliver Aldulea (oli at bv dot ro)
+ *
+ *	This code is based on the RISCom/8 multiport serial driver written
+ *	by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial
+ *	driver, written by Linus Torvalds, Theodore T'so and others.
+ *	The Aurora multiport programming info was obtained mainly from the
+ *	Cirrus Logic CD180 documentation (available on the web), and by
+ *	doing heavy tests on the board. Many thanks to Eddie C. Dost for the
+ *	help on the sbus interface.
+ *
+ *	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.0
+ *
+ *	This is the first public release.
+ *
+ *	Most of the information you need is in the aurora.h file. Please
+ *	read that file before reading this one.
+ *
+ *	Several parts of the code do not have comments yet.
+ * 
+ * n.b.  The board can support 115.2 bit rates, but only on a few
+ * ports. The total badwidth of one chip (ports 0-7 or 8-15) is equal
+ * to OSC_FREQ div 16. In case of my board, each chip can take 6
+ * channels of 115.2 kbaud.  This information is not well-tested.
+ * 
+ * Fixed to use tty_get_baud_rate().
+ *   Theodore Ts'o <tytso@mit.edu>, 2001-Oct-12
+ */
+
+#include <linux/module.h>
+
+#include <linux/errno.h>
+#include <linux/sched.h>
+#ifdef AURORA_INT_DEBUG
+#include <linux/timer.h>
+#endif
+#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/mm.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/kdebug.h>
+#include <asm/sbus.h>
+#include <asm/uaccess.h>
+
+#include "aurora.h"
+#include "cd180.h"
+
+unsigned char irqs[4] = {
+	0, 0, 0, 0
+};
+
+#ifdef AURORA_INT_DEBUG
+int irqhit=0;
+#endif
+
+#ifndef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+#endif
+
+static struct tty_driver *aurora_driver;
+static struct Aurora_board aurora_board[AURORA_NBOARD] = {
+	{0,},
+};
+
+static struct Aurora_port aurora_port[AURORA_TNPORTS] =  {
+	{ 0, },
+};
+
+/* no longer used. static struct Aurora_board * IRQ_to_board[16] = { NULL, } ;*/
+static unsigned char * tmp_buf = NULL;
+static DECLARE_MUTEX(tmp_buf_sem);
+
+DECLARE_TASK_QUEUE(tq_aurora);
+
+static inline int aurora_paranoia_check(struct Aurora_port const * port,
+				    char *name, const char *routine)
+{
+#ifdef AURORA_PARANOIA_CHECK
+	static const char *badmagic =
+		KERN_DEBUG "aurora: Warning: bad aurora port magic number for device %s in %s\n";
+	static const char *badinfo =
+		KERN_DEBUG "aurora: Warning: null aurora port for device %s in %s\n";
+
+	if (!port) {
+		printk(badinfo, name, routine);
+		return 1;
+	}
+	if (port->magic != AURORA_MAGIC) {
+		printk(badmagic, name, routine);
+		return 1;
+	}
+#endif
+	return 0;
+}
+
+/*
+ * 
+ *  Service functions for aurora driver.
+ * 
+ */
+
+/* Get board number from pointer */
+extern inline int board_No (struct Aurora_board const * bp)
+{
+	return bp - aurora_board;
+}
+
+/* Get port number from pointer */
+extern inline int port_No (struct Aurora_port const * port)
+{
+	return AURORA_PORT(port - aurora_port); 
+}
+
+/* Get pointer to board from pointer to port */
+extern inline struct Aurora_board * port_Board(struct Aurora_port const * port)
+{
+	return &aurora_board[AURORA_BOARD(port - aurora_port)];
+}
+
+/* Wait for Channel Command Register ready */
+extern inline void aurora_wait_CCR(struct aurora_reg128 * r)
+{
+	unsigned long delay;
+
+#ifdef AURORA_DEBUG
+printk("aurora_wait_CCR\n");
+#endif
+	/* FIXME: need something more descriptive than 100000 :) */
+	for (delay = 100000; delay; delay--) 
+		if (!sbus_readb(&r->r[CD180_CCR]))
+			return;
+	printk(KERN_DEBUG "aurora: Timeout waiting for CCR.\n");
+}
+
+/*
+ *  aurora probe functions.
+ */
+
+/* Must be called with enabled interrupts */
+extern inline void aurora_long_delay(unsigned long delay)
+{
+	unsigned long i;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_long_delay: start\n");
+#endif
+	for (i = jiffies + delay; time_before(jiffies, i); ) ;
+#ifdef AURORA_DEBUG
+	printk("aurora_long_delay: end\n");
+#endif
+}
+
+/* Reset and setup CD180 chip */
+static int aurora_init_CD180(struct Aurora_board * bp, int chip)
+{
+	unsigned long flags;
+	int id;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_init_CD180: start %d:%d\n",
+	       board_No(bp), chip);
+#endif
+	save_flags(flags); cli();
+	sbus_writeb(0, &bp->r[chip]->r[CD180_CAR]);
+	sbus_writeb(0, &bp->r[chip]->r[CD180_GSVR]);
+
+	/* Wait for CCR ready        */
+	aurora_wait_CCR(bp->r[chip]);
+
+	/* Reset CD180 chip          */
+	sbus_writeb(CCR_HARDRESET, &bp->r[chip]->r[CD180_CCR]);
+	udelay(1);
+	sti();
+	id=1000;
+	while((--id) &&
+	      (sbus_readb(&bp->r[chip]->r[CD180_GSVR])!=0xff))udelay(100);
+	if(!id) {
+		printk(KERN_ERR "aurora%d: Chip %d failed init.\n",
+		       board_No(bp), chip);
+		restore_flags(flags);
+		return(-1);
+	}
+	cli();
+	sbus_writeb((board_No(bp)<<5)|((chip+1)<<3),
+		    &bp->r[chip]->r[CD180_GSVR]); /* Set ID for this chip      */
+	sbus_writeb(0x80|bp->ACK_MINT,
+		    &bp->r[chip]->r[CD180_MSMR]); /* Prio for modem intr       */
+	sbus_writeb(0x80|bp->ACK_TINT,
+		    &bp->r[chip]->r[CD180_TSMR]); /* Prio for transmitter intr */
+	sbus_writeb(0x80|bp->ACK_RINT,
+		    &bp->r[chip]->r[CD180_RSMR]); /* Prio for receiver intr    */
+	/* Setting up prescaler. We need 4 tick per 1 ms */
+	sbus_writeb((bp->oscfreq/(1000000/AURORA_TPS)) >> 8,
+		    &bp->r[chip]->r[CD180_PPRH]);
+	sbus_writeb((bp->oscfreq/(1000000/AURORA_TPS)) & 0xff,
+		    &bp->r[chip]->r[CD180_PPRL]);
+
+	sbus_writeb(SRCR_AUTOPRI|SRCR_GLOBPRI,
+		    &bp->r[chip]->r[CD180_SRCR]);
+
+	id = sbus_readb(&bp->r[chip]->r[CD180_GFRCR]);
+	printk(KERN_INFO "aurora%d: Chip %d id %02x: ",
+	       board_No(bp), chip,id);
+	if(sbus_readb(&bp->r[chip]->r[CD180_SRCR]) & 128) {
+		switch (id) {
+			case 0x82:printk("CL-CD1864 rev A\n");break;
+			case 0x83:printk("CL-CD1865 rev A\n");break;
+			case 0x84:printk("CL-CD1865 rev B\n");break;
+			case 0x85:printk("CL-CD1865 rev C\n");break;
+			default:printk("Unknown.\n");
+		};
+	} else {
+		switch (id) {
+			case 0x81:printk("CL-CD180 rev B\n");break;
+			case 0x82:printk("CL-CD180 rev C\n");break;
+			default:printk("Unknown.\n");
+		};
+	}
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_init_CD180: end\n");
+#endif
+	return 0;
+}
+
+static int valid_irq(unsigned char irq)
+{
+int i;
+for(i=0;i<TYPE_1_IRQS;i++)
+	if (type_1_irq[i]==irq) return 1;
+return 0;
+}
+
+static irqreturn_t aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs);
+
+/* Main probing routine, also sets irq. */
+static int aurora_probe(void)
+{
+	struct sbus_bus *sbus;
+	struct sbus_dev *sdev;
+	int grrr;
+	char buf[30];
+	int bn = 0;
+	struct Aurora_board *bp;
+
+	for_each_sbus(sbus) {
+		for_each_sbusdev(sdev, sbus) {
+/*			printk("Try: %x %s\n",sdev,sdev->prom_name);*/
+			if (!strcmp(sdev->prom_name, "sio16")) {
+#ifdef AURORA_DEBUG
+				printk(KERN_INFO "aurora: sio16 at %p\n",sdev);
+#endif
+				if((sdev->reg_addrs[0].reg_size!=1) &&
+				   (sdev->reg_addrs[1].reg_size!=128) &&
+				   (sdev->reg_addrs[2].reg_size!=128) &&
+				   (sdev->reg_addrs[3].reg_size!=4)) {
+				   	printk(KERN_ERR "aurora%d: registers' sizes "
+					       "do not match.\n", bn);
+				   	break;
+				}
+				bp = &aurora_board[bn];
+				bp->r0 = (struct aurora_reg1 *)
+					sbus_ioremap(&sdev->resource[0], 0,
+						     sdev->reg_addrs[0].reg_size,
+						     "sio16");
+				if (bp->r0 == NULL) {
+					printk(KERN_ERR "aurora%d: can't map "
+					       "reg_addrs[0]\n", bn);
+					break;
+				}
+#ifdef AURORA_DEBUG
+				printk("Map reg 0: %p\n", bp->r0);
+#endif
+				bp->r[0] = (struct aurora_reg128 *)
+					sbus_ioremap(&sdev->resource[1], 0,
+						     sdev->reg_addrs[1].reg_size,
+						     "sio16");
+				if (bp->r[0] == NULL) {
+					printk(KERN_ERR "aurora%d: can't map "
+					       "reg_addrs[1]\n", bn);
+					break;
+				}
+#ifdef AURORA_DEBUG
+				printk("Map reg 1: %p\n", bp->r[0]);
+#endif
+				bp->r[1] = (struct aurora_reg128 *)
+					sbus_ioremap(&sdev->resource[2], 0,
+						     sdev->reg_addrs[2].reg_size,
+						     "sio16");
+				if (bp->r[1] == NULL) {
+					printk(KERN_ERR "aurora%d: can't map "
+					       "reg_addrs[2]\n", bn);
+					break;
+				}
+#ifdef AURORA_DEBUG
+				printk("Map reg 2: %p\n", bp->r[1]);
+#endif
+				bp->r3 = (struct aurora_reg4 *)
+					sbus_ioremap(&sdev->resource[3], 0,
+						     sdev->reg_addrs[3].reg_size,
+						     "sio16");
+				if (bp->r3 == NULL) {
+					printk(KERN_ERR "aurora%d: can't map "
+					       "reg_addrs[3]\n", bn);
+					break;
+				}
+#ifdef AURORA_DEBUG
+				printk("Map reg 3: %p\n", bp->r3);
+#endif
+				/* Variables setup */
+				bp->flags = 0;
+#ifdef AURORA_DEBUG
+				grrr=prom_getint(sdev->prom_node,"intr");
+				printk("intr pri %d\n", grrr);
+#endif
+				if ((bp->irq=irqs[bn]) && valid_irq(bp->irq) &&
+				    !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+					free_irq(bp->irq|0x30, bp);
+				} else
+				if ((bp->irq=prom_getint(sdev->prom_node, "bintr")) && valid_irq(bp->irq) &&
+				    !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+					free_irq(bp->irq|0x30, bp);
+				} else
+				if ((bp->irq=prom_getint(sdev->prom_node, "intr")) && valid_irq(bp->irq) &&
+				    !request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+					free_irq(bp->irq|0x30, bp);
+				} else
+				for(grrr=0;grrr<TYPE_1_IRQS;grrr++) {
+					if ((bp->irq=type_1_irq[grrr])&&!request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ, "sio16", bp)) {
+						free_irq(bp->irq|0x30, bp);
+						break;
+					} else {
+					printk(KERN_ERR "aurora%d: Could not get an irq for this board !!!\n",bn);
+					bp->flags=0xff;
+					}
+				}
+				if(bp->flags==0xff)break;
+				printk(KERN_INFO "aurora%d: irq %d\n",bn,bp->irq&0x0f);
+				buf[0]=0;
+				grrr=prom_getproperty(sdev->prom_node,"dtr_rts",buf,sizeof(buf));
+				if(!strcmp(buf,"swapped")){
+					printk(KERN_INFO "aurora%d: Swapped DTR and RTS\n",bn);
+					bp->DTR=MSVR_RTS;
+					bp->RTS=MSVR_DTR;
+					bp->MSVDTR=CD180_MSVRTS;
+					bp->MSVRTS=CD180_MSVDTR;
+					bp->flags|=AURORA_BOARD_DTR_FLOW_OK;
+					}else{
+					#ifdef AURORA_FORCE_DTR_FLOW
+					printk(KERN_INFO "aurora%d: Forcing swapped DTR-RTS\n",bn);
+					bp->DTR=MSVR_RTS;
+					bp->RTS=MSVR_DTR;
+					bp->MSVDTR=CD180_MSVRTS;
+					bp->MSVRTS=CD180_MSVDTR;
+					bp->flags|=AURORA_BOARD_DTR_FLOW_OK;
+					#else
+					printk(KERN_INFO "aurora%d: Normal DTR and RTS\n",bn);
+					bp->DTR=MSVR_DTR;
+					bp->RTS=MSVR_RTS;
+					bp->MSVDTR=CD180_MSVDTR;
+					bp->MSVRTS=CD180_MSVRTS;
+					#endif
+				}
+				bp->oscfreq=prom_getint(sdev->prom_node,"clk")*100;
+				printk(KERN_INFO "aurora%d: Oscillator: %d Hz\n",bn,bp->oscfreq);
+				grrr=prom_getproperty(sdev->prom_node,"chip",buf,sizeof(buf));
+				printk(KERN_INFO "aurora%d: Chips: %s\n",bn,buf);
+				grrr=prom_getproperty(sdev->prom_node,"manu",buf,sizeof(buf));
+				printk(KERN_INFO "aurora%d: Manufacturer: %s\n",bn,buf);
+				grrr=prom_getproperty(sdev->prom_node,"model",buf,sizeof(buf));
+				printk(KERN_INFO "aurora%d: Model: %s\n",bn,buf);
+				grrr=prom_getproperty(sdev->prom_node,"rev",buf,sizeof(buf));
+				printk(KERN_INFO "aurora%d: Revision: %s\n",bn,buf);
+				grrr=prom_getproperty(sdev->prom_node,"mode",buf,sizeof(buf));
+				printk(KERN_INFO "aurora%d: Mode: %s\n",bn,buf);
+				#ifdef MODULE
+				bp->count=0;
+				#endif
+				bp->flags = AURORA_BOARD_PRESENT;
+				/* hardware ack */
+				bp->ACK_MINT=1;
+				bp->ACK_TINT=2;
+				bp->ACK_RINT=3;
+				bn++;
+			}
+		}
+	}
+	return bn;
+}
+
+static void aurora_release_io_range(struct Aurora_board *bp)
+{
+	sbus_iounmap((unsigned long)bp->r0, 1);
+	sbus_iounmap((unsigned long)bp->r[0], 128);
+	sbus_iounmap((unsigned long)bp->r[1], 128);
+	sbus_iounmap((unsigned long)bp->r3, 4);
+}
+
+extern inline void aurora_mark_event(struct Aurora_port * port, int event)
+{
+#ifdef AURORA_DEBUG
+	printk("aurora_mark_event: start\n");
+#endif
+	set_bit(event, &port->event);
+	queue_task(&port->tqueue, &tq_aurora);
+	mark_bh(AURORA_BH);
+#ifdef AURORA_DEBUG
+	printk("aurora_mark_event: end\n");
+#endif
+}
+
+static __inline__ struct Aurora_port * aurora_get_port(struct Aurora_board const * bp,
+						       int chip,
+						       unsigned char const *what)
+{
+	unsigned char channel;
+	struct Aurora_port * port;
+
+	channel = ((chip << 3) |
+		   ((sbus_readb(&bp->r[chip]->r[CD180_GSCR]) & GSCR_CHAN) >> GSCR_CHAN_OFF));
+	port = &aurora_port[board_No(bp) * AURORA_NPORT * AURORA_NCD180 + channel];
+	if (port->flags & ASYNC_INITIALIZED)
+		return port;
+
+	printk(KERN_DEBUG "aurora%d: %s interrupt from invalid port %d\n",
+	       board_No(bp), what, channel);
+	return NULL;
+}
+
+static void aurora_receive_exc(struct Aurora_board const * bp, int chip)
+{
+	struct Aurora_port *port;
+	struct tty_struct *tty;
+	unsigned char status;
+	unsigned char ch;
+	
+	if (!(port = aurora_get_port(bp, chip, "Receive_x")))
+		return;
+
+	tty = port->tty;
+	if (tty->flip.count >= TTY_FLIPBUF_SIZE)  {
+#ifdef AURORA_INTNORM
+		printk("aurora%d: port %d: Working around flip buffer overflow.\n",
+		       board_No(bp), port_No(port));
+#endif
+		return;
+	}
+	
+#ifdef AURORA_REPORT_OVERRUN	
+	status = sbus_readb(&bp->r[chip]->r[CD180_RCSR]);
+	if (status & RCSR_OE)  {
+		port->overrun++;
+#if 1
+		printk("aurora%d: port %d: Overrun. Total %ld overruns.\n",
+		       board_No(bp), port_No(port), port->overrun);
+#endif		
+	}
+	status &= port->mark_mask;
+#else	
+	status = sbus_readb(&bp->r[chip]->r[CD180_RCSR]) & port->mark_mask;
+#endif	
+	ch = sbus_readb(&bp->r[chip]->r[CD180_RDR]);
+	if (!status)
+		return;
+
+	if (status & RCSR_TOUT)  {
+/*		printk("aurora%d: port %d: Receiver timeout. Hardware problems ?\n",
+		       board_No(bp), port_No(port));*/
+		return;
+		
+	} else if (status & RCSR_BREAK)  {
+		printk(KERN_DEBUG "aurora%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++;
+	queue_task(&tty->flip.tqueue, &tq_timer);
+}
+
+static void aurora_receive(struct Aurora_board const * bp, int chip)
+{
+	struct Aurora_port *port;
+	struct tty_struct *tty;
+	unsigned char count,cnt;
+
+	if (!(port = aurora_get_port(bp, chip, "Receive")))
+		return;
+	
+	tty = port->tty;
+	
+	count = sbus_readb(&bp->r[chip]->r[CD180_RDCR]);
+
+#ifdef AURORA_REPORT_FIFO
+	port->hits[count > 8 ? 9 : count]++;
+#endif
+
+	while (count--)  {
+		if (tty->flip.count >= TTY_FLIPBUF_SIZE)  {
+#ifdef AURORA_INTNORM
+			printk("aurora%d: port %d: Working around flip buffer overflow.\n",
+			       board_No(bp), port_No(port));
+#endif
+			break;
+		}
+		cnt = sbus_readb(&bp->r[chip]->r[CD180_RDR]);
+		*tty->flip.char_buf_ptr++ = cnt;
+		*tty->flip.flag_buf_ptr++ = 0;
+		tty->flip.count++;
+	}
+	queue_task(&tty->flip.tqueue, &tq_timer);
+}
+
+static void aurora_transmit(struct Aurora_board const * bp, int chip)
+{
+	struct Aurora_port *port;
+	struct tty_struct *tty;
+	unsigned char count;
+	
+	if (!(port = aurora_get_port(bp, chip, "Transmit")))
+		return;
+		
+	tty = port->tty;
+	
+	if (port->SRER & SRER_TXEMPTY)  {
+		/* FIFO drained */
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		port->SRER &= ~SRER_TXEMPTY;
+		sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+		return;
+	}
+	
+	if ((port->xmit_cnt <= 0 && !port->break_length)
+	    || tty->stopped || tty->hw_stopped)  {
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		port->SRER &= ~SRER_TXRDY;
+		sbus_writeb(port->SRER,
+			    &bp->r[chip]->r[CD180_SRER]);
+		return;
+	}
+	
+	if (port->break_length)  {
+		if (port->break_length > 0)  {
+			if (port->COR2 & COR2_ETC)  {
+				sbus_writeb(CD180_C_ESC,
+					    &bp->r[chip]->r[CD180_TDR]);
+				sbus_writeb(CD180_C_SBRK,
+					    &bp->r[chip]->r[CD180_TDR]);
+				port->COR2 &= ~COR2_ETC;
+			}
+			count = MIN(port->break_length, 0xff);
+			sbus_writeb(CD180_C_ESC,
+				    &bp->r[chip]->r[CD180_TDR]);
+			sbus_writeb(CD180_C_DELAY,
+				    &bp->r[chip]->r[CD180_TDR]);
+			sbus_writeb(count,
+				    &bp->r[chip]->r[CD180_TDR]);
+			if (!(port->break_length -= count))
+				port->break_length--;
+		} else  {
+			sbus_writeb(CD180_C_ESC,
+				    &bp->r[chip]->r[CD180_TDR]);
+			sbus_writeb(CD180_C_EBRK,
+				    &bp->r[chip]->r[CD180_TDR]);
+			sbus_writeb(port->COR2,
+				    &bp->r[chip]->r[CD180_COR2]);
+			aurora_wait_CCR(bp->r[chip]);
+			sbus_writeb(CCR_CORCHG2,
+				    &bp->r[chip]->r[CD180_CCR]);
+			port->break_length = 0;
+		}
+		return;
+	}
+	
+	count = CD180_NFIFO;
+	do {
+		u8 byte = port->xmit_buf[port->xmit_tail++];
+
+		sbus_writeb(byte, &bp->r[chip]->r[CD180_TDR]);
+		port->xmit_tail = port->xmit_tail & (SERIAL_XMIT_SIZE-1);
+		if (--port->xmit_cnt <= 0)
+			break;
+	} while (--count > 0);
+	
+	if (port->xmit_cnt <= 0)  {
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		port->SRER &= ~SRER_TXRDY;
+		sbus_writeb(port->SRER,
+			    &bp->r[chip]->r[CD180_SRER]);
+	}
+	if (port->xmit_cnt <= port->wakeup_chars)
+		aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+}
+
+static void aurora_check_modem(struct Aurora_board const * bp, int chip)
+{
+	struct Aurora_port *port;
+	struct tty_struct *tty;
+	unsigned char mcr;
+	
+	if (!(port = aurora_get_port(bp, chip, "Modem")))
+		return;
+		
+	tty = port->tty;
+	
+	mcr = sbus_readb(&bp->r[chip]->r[CD180_MCR]);
+	if (mcr & MCR_CDCHG)  {
+		if (sbus_readb(&bp->r[chip]->r[CD180_MSVR]) & MSVR_CD) 
+			wake_up_interruptible(&port->open_wait);
+		else
+			schedule_task(&port->tqueue_hangup);
+	}
+	
+/* We don't have such things yet. My aurora board has DTR and RTS swapped, but that doesn't count in this driver. Let's hope
+ * Aurora didn't made any boards with CTS or DSR broken...
+ */
+/* #ifdef AURORA_BRAIN_DAMAGED_CTS
+	if (mcr & MCR_CTSCHG)  {
+		if (aurora_in(bp, CD180_MSVR) & MSVR_CTS)  {
+			tty->hw_stopped = 0;
+			port->SRER |= SRER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else  {
+			tty->hw_stopped = 1;
+			port->SRER &= ~SRER_TXRDY;
+		}
+		sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	}
+	if (mcr & MCR_DSRCHG)  {
+		if (aurora_in(bp, CD180_MSVR) & MSVR_DSR)  {
+			tty->hw_stopped = 0;
+			port->SRER |= SRER_TXRDY;
+			if (port->xmit_cnt <= port->wakeup_chars)
+				aurora_mark_event(port, RS_EVENT_WRITE_WAKEUP);
+		} else  {
+			tty->hw_stopped = 1;
+			port->SRER &= ~SRER_TXRDY;
+		}
+		sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	}
+#endif AURORA_BRAIN_DAMAGED_CTS */
+	
+	/* Clear change bits */
+	sbus_writeb(0, &bp->r[chip]->r[CD180_MCR]);
+}
+
+/* The main interrupt processing routine */
+static irqreturn_t aurora_interrupt(int irq, void * dev_id, struct pt_regs * regs)
+{
+	unsigned char status;
+	unsigned char ack,chip/*,chip_id*/;
+	struct Aurora_board * bp = (struct Aurora_board *) dev_id;
+	unsigned long loop = 0;
+
+#ifdef AURORA_INT_DEBUG
+	printk("IRQ%d %d\n",irq,++irqhit);
+#ifdef AURORA_FLOODPRO
+	if (irqhit>=AURORA_FLOODPRO)
+		sbus_writeb(8, &bp->r0->r);
+#endif
+#endif
+	
+/* old	bp = IRQ_to_board[irq&0x0f];*/
+	
+	if (!bp || !(bp->flags & AURORA_BOARD_ACTIVE))
+		return IRQ_NONE;
+
+/*	The while() below takes care of this.
+	status = sbus_readb(&bp->r[0]->r[CD180_SRSR]);
+#ifdef AURORA_INT_DEBUG
+	printk("mumu: %02x\n", status);
+#endif
+	if (!(status&SRSR_ANYINT))
+		return IRQ_NONE; * Nobody has anything to say, so exit *
+*/
+	while ((loop++ < 48) &&
+	       (status = sbus_readb(&bp->r[0]->r[CD180_SRSR]) & SRSR_ANYINT)){
+#ifdef AURORA_INT_DEBUG
+		printk("SRSR: %02x\n", status);
+#endif
+		if (status & SRSR_REXT) {
+			ack = sbus_readb(&bp->r3->r[bp->ACK_RINT]);
+#ifdef AURORA_INT_DEBUG
+			printk("R-ACK %02x\n", ack);
+#endif
+			if ((ack >> 5) == board_No(bp)) {
+				if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+					if ((ack&GSVR_ITMASK)==GSVR_IT_RGD) {
+						aurora_receive(bp,chip);
+						sbus_writeb(0,
+							 &bp->r[chip]->r[CD180_EOSRR]);
+					} else if ((ack & GSVR_ITMASK) == GSVR_IT_REXC) {
+						aurora_receive_exc(bp,chip);
+						sbus_writeb(0,
+							 &bp->r[chip]->r[CD180_EOSRR]);
+					}
+				}
+			}
+		} else if (status & SRSR_TEXT) {
+			ack = sbus_readb(&bp->r3->r[bp->ACK_TINT]);
+#ifdef AURORA_INT_DEBUG
+			printk("T-ACK %02x\n", ack);
+#endif
+			if ((ack >> 5) == board_No(bp)) {
+				if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+					if ((ack&GSVR_ITMASK)==GSVR_IT_TX) {
+						aurora_transmit(bp,chip);
+						sbus_writeb(0,
+							 &bp->r[chip]->r[CD180_EOSRR]);
+					}
+				}
+			}
+		} else if (status & SRSR_MEXT) {
+			ack = sbus_readb(&bp->r3->r[bp->ACK_MINT]);
+#ifdef AURORA_INT_DEBUG
+			printk("M-ACK %02x\n", ack);
+#endif
+			if ((ack >> 5) == board_No(bp)) {
+				if ((chip = ((ack>>3)&3)-1) < AURORA_NCD180) {
+					if ((ack&GSVR_ITMASK)==GSVR_IT_MDM) {
+						aurora_check_modem(bp,chip);
+						sbus_writeb(0,
+							 &bp->r[chip]->r[CD180_EOSRR]);
+					}
+				}
+			}
+		}
+	}
+/* I guess this faster code can be used with CD1865, using AUROPRI and GLOBPRI. */
+#if 0
+	while ((loop++ < 48)&&(status=bp->r[0]->r[CD180_SRSR]&SRSR_ANYINT)){
+#ifdef AURORA_INT_DEBUG
+		printk("SRSR: %02x\n",status);
+#endif
+		ack = sbus_readb(&bp->r3->r[0]);
+#ifdef AURORA_INT_DEBUG
+		printk("ACK: %02x\n",ack);
+#endif
+		if ((ack>>5)==board_No(bp)) {
+			if ((chip=((ack>>3)&3)-1) < AURORA_NCD180) {
+				ack&=GSVR_ITMASK;
+				if (ack==GSVR_IT_RGD) {
+					aurora_receive(bp,chip);
+					sbus_writeb(0,
+						    &bp->r[chip]->r[CD180_EOSRR]);
+				} else if (ack==GSVR_IT_REXC) {
+					aurora_receive_exc(bp,chip);
+					sbus_writeb(0,
+						    &bp->r[chip]->r[CD180_EOSRR]);
+				} else if (ack==GSVR_IT_TX) {
+					aurora_transmit(bp,chip);
+					sbus_writeb(0,
+						    &bp->r[chip]->r[CD180_EOSRR]);
+				} else if (ack==GSVR_IT_MDM) {
+					aurora_check_modem(bp,chip);
+					sbus_writeb(0,
+						    &bp->r[chip]->r[CD180_EOSRR]);
+				}
+			}
+		}
+	}
+#endif
+
+/* This is the old handling routine, used in riscom8 for only one CD180. I keep it here for reference. */
+#if 0
+	for(chip=0;chip<AURORA_NCD180;chip++){
+		chip_id=(board_No(bp)<<5)|((chip+1)<<3);
+		loop=0;
+		while ((loop++ < 1) &&
+		       ((status = sbus_readb(&bp->r[chip]->r[CD180_SRSR])) &
+			(SRSR_TEXT | SRSR_MEXT | SRSR_REXT))) {
+
+			if (status & SRSR_REXT) {
+				ack = sbus_readb(&bp->r3->r[bp->ACK_RINT]);
+				if (ack == (chip_id | GSVR_IT_RGD)) {
+#ifdef AURORA_INTMSG
+					printk("RX ACK\n");
+#endif
+					aurora_receive(bp,chip);
+				} else if (ack == (chip_id | GSVR_IT_REXC)) {
+#ifdef AURORA_INTMSG
+					printk("RXC ACK\n");
+#endif
+					aurora_receive_exc(bp,chip);
+				} else {
+#ifdef AURORA_INTNORM
+					printk("aurora%d-%d: Bad receive ack 0x%02x.\n",
+					       board_No(bp), chip, ack);
+#endif
+				}
+			} else if (status & SRSR_TEXT) {
+				ack = sbus_readb(&bp->r3->r[bp->ACK_TINT]);
+				if (ack == (chip_id | GSVR_IT_TX)){
+#ifdef AURORA_INTMSG
+					printk("TX ACK\n");
+#endif
+					aurora_transmit(bp,chip);
+				} else {
+#ifdef AURORA_INTNORM
+					printk("aurora%d-%d: Bad transmit ack 0x%02x.\n",
+					       board_No(bp), chip, ack);
+#endif
+				}
+			} else  if (status & SRSR_MEXT)  {
+				ack = sbus_readb(&bp->r3->r[bp->ACK_MINT]);
+				if (ack == (chip_id | GSVR_IT_MDM)){
+#ifdef AURORA_INTMSG
+					printk("MDM ACK\n");
+#endif
+					aurora_check_modem(bp,chip);
+				} else {
+#ifdef AURORA_INTNORM
+					printk("aurora%d-%d: Bad modem ack 0x%02x.\n",
+					       board_No(bp), chip, ack);
+#endif
+				}
+			}
+			sbus_writeb(0, &bp->r[chip]->r[CD180_EOSRR]);
+		}
+	}
+#endif
+
+	return IRQ_HANDLED;
+}
+
+#ifdef AURORA_INT_DEBUG
+static void aurora_timer (unsigned long ignored);
+
+static struct timer_list aurora_poll_timer =
+			TIMER_INITIALIZER(aurora_timer, 0, 0);
+
+static void
+aurora_timer (unsigned long ignored)
+{
+	unsigned long flags;
+	int i;
+
+	save_flags(flags); cli();
+
+	printk("SRSR: %02x,%02x - ",
+	       sbus_readb(&aurora_board[0].r[0]->r[CD180_SRSR]),
+	       sbus_readb(&aurora_board[0].r[1]->r[CD180_SRSR]));
+	for (i = 0; i < 4; i++) {
+		udelay(1);
+		printk("%02x ",
+		       sbus_readb(&aurora_board[0].r3->r[i]));
+	}
+	printk("\n");
+
+	aurora_poll_timer.expires = jiffies + 300;
+	add_timer (&aurora_poll_timer);
+
+	restore_flags(flags);
+}
+#endif
+
+/*
+ *  Routines for open & close processing.
+ */
+
+/* Called with disabled interrupts */
+static int aurora_setup_board(struct Aurora_board * bp)
+{
+	int error;
+	
+#ifdef AURORA_ALLIRQ
+	int i;
+	for (i = 0; i < AURORA_ALLIRQ; i++) {
+		error = request_irq(allirq[i]|0x30, aurora_interrupt, SA_SHIRQ,
+				    "sio16", bp);
+		if (error)
+			printk(KERN_ERR "IRQ%d request error %d\n",
+			       allirq[i], error);
+	}
+#else
+	error = request_irq(bp->irq|0x30, aurora_interrupt, SA_SHIRQ,
+			    "sio16", bp);
+	if (error) {
+		printk(KERN_ERR "IRQ request error %d\n", error);
+		return error;
+	}
+#endif
+	/* Board reset */
+	sbus_writeb(0, &bp->r0->r);
+	udelay(1);
+	if (bp->flags & AURORA_BOARD_TYPE_2) {
+		/* unknown yet */
+	} else {
+		sbus_writeb((AURORA_CFG_ENABLE_IO | AURORA_CFG_ENABLE_IRQ |
+			     (((bp->irq)&0x0f)>>2)),
+			    &bp->r0->r);
+	}
+	udelay(10000);
+
+	if (aurora_init_CD180(bp,0))error=1;error=0;
+	if (aurora_init_CD180(bp,1))error++;
+	if (error == AURORA_NCD180) {
+		printk(KERN_ERR "Both chips failed initialisation.\n");
+		return -EIO;
+	}
+
+#ifdef AURORA_INT_DEBUG
+	aurora_poll_timer.expires= jiffies + 1;
+	add_timer(&aurora_poll_timer);
+#endif
+#ifdef AURORA_DEBUG
+	printk("aurora_setup_board: end\n");
+#endif
+	return 0;
+}
+
+/* Called with disabled interrupts */
+static void aurora_shutdown_board(struct Aurora_board *bp)
+{
+	int i;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_shutdown_board: start\n");
+#endif
+
+#ifdef AURORA_INT_DEBUG
+	del_timer(&aurora_poll_timer);
+#endif
+
+#ifdef AURORA_ALLIRQ
+	for(i=0;i<AURORA_ALLIRQ;i++){
+		free_irq(allirq[i]|0x30, bp);
+/*		IRQ_to_board[allirq[i]&0xf] = NULL;*/
+	}
+#else
+	free_irq(bp->irq|0x30, bp);
+/*	IRQ_to_board[bp->irq&0xf] = NULL;*/
+#endif	
+	/* Drop all DTR's */
+	for(i=0;i<16;i++){
+		sbus_writeb(i & 7, &bp->r[i>>3]->r[CD180_CAR]);
+		udelay(1);
+		sbus_writeb(0, &bp->r[i>>3]->r[CD180_MSVR]);
+		udelay(1);
+	}
+	/* Board shutdown */
+	sbus_writeb(0, &bp->r0->r);
+
+#ifdef AURORA_DEBUG
+	printk("aurora_shutdown_board: end\n");
+#endif
+}
+
+/* Setting up port characteristics. 
+ * Must be called with disabled interrupts
+ */
+static void aurora_change_speed(struct Aurora_board *bp, struct Aurora_port *port)
+{
+	struct tty_struct *tty;
+	unsigned long baud;
+	long tmp;
+	unsigned char cor1 = 0, cor3 = 0;
+	unsigned char mcor1 = 0, mcor2 = 0,chip;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_change_speed: start\n");
+#endif
+	if (!(tty = port->tty) || !tty->termios)
+		return;
+		
+	chip = AURORA_CD180(port_No(port));
+
+	port->SRER  = 0;
+	port->COR2 = 0;
+	port->MSVR = MSVR_RTS|MSVR_DTR;
+	
+	baud = tty_get_baud_rate(tty);
+	
+	/* Select port on the board */
+	sbus_writeb(port_No(port) & 7,
+		    &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+	
+	if (!baud)  {
+		/* Drop DTR & exit */
+		port->MSVR &= ~(bp->DTR|bp->RTS);
+		sbus_writeb(port->MSVR,
+			    &bp->r[chip]->r[CD180_MSVR]);
+		return;
+	} else  {
+		/* Set DTR on */
+		port->MSVR |= bp->DTR;
+		sbus_writeb(port->MSVR,
+			    &bp->r[chip]->r[CD180_MSVR]);
+	}
+	
+	/* Now we must calculate some speed dependent things. */
+	
+	/* Set baud rate for port. */
+	tmp = (((bp->oscfreq + baud/2) / baud +
+		CD180_TPC/2) / CD180_TPC);
+
+/*	tmp = (bp->oscfreq/7)/baud;
+	if((tmp%10)>4)tmp=tmp/10+1;else tmp=tmp/10;*/
+/*	printk("Prescaler period: %d\n",tmp);*/
+
+	sbus_writeb((tmp >> 8) & 0xff,
+		    &bp->r[chip]->r[CD180_RBPRH]);
+	sbus_writeb((tmp >> 8) & 0xff,
+		    &bp->r[chip]->r[CD180_TBPRH]);
+	sbus_writeb(tmp & 0xff, &bp->r[chip]->r[CD180_RBPRL]);
+	sbus_writeb(tmp & 0xff, &bp->r[chip]->r[CD180_TBPRL]);
+	
+	baud = (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 = (AURORA_TPS + AURORA_TPS/2 + baud/2) / baud;
+	tmp = (tmp > 0xff) ? 0xff : tmp;
+	sbus_writeb(tmp, &bp->r[chip]->r[CD180_RTPR]);
+	
+	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 AURORA_BRAIN_DAMAGED_CTS
+		port->SRER |= SRER_DSR | SRER_CTS;
+		mcor1 |= MCOR1_DSRZD | MCOR1_CTSZD;
+		mcor2 |= MCOR2_DSROD | MCOR2_CTSOD;
+		tty->hw_stopped = !(aurora_in(bp, CD180_MSVR) & (MSVR_CTS|MSVR_DSR));
+#else*/
+		port->COR2 |= COR2_CTSAE;
+/*#endif*/
+		if (bp->flags&AURORA_BOARD_DTR_FLOW_OK) {
+			mcor1 |= AURORA_RXTH;
+		}
+	}
+	/* 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;
+		sbus_writeb(START_CHAR(tty),
+			    &bp->r[chip]->r[CD180_SCHR1]);
+		sbus_writeb(STOP_CHAR(tty),
+			    &bp->r[chip]->r[CD180_SCHR2]);
+		sbus_writeb(START_CHAR(tty),
+			    &bp->r[chip]->r[CD180_SCHR3]);
+		sbus_writeb(STOP_CHAR(tty),
+			    &bp->r[chip]->r[CD180_SCHR4]);
+	}
+	if (!C_CLOCAL(tty))  {
+		/* Enable CD check */
+		port->SRER |= SRER_CD;
+		mcor1 |= MCOR1_CDZD;
+		mcor2 |= MCOR2_CDOD;
+	}
+	
+	if (C_CREAD(tty)) 
+		/* Enable receiver */
+		port->SRER |= SRER_RXD;
+	
+	/* Set input FIFO size (1-8 bytes) */
+	cor3 |= AURORA_RXFIFO; 
+	/* Setting up CD180 channel registers */
+	sbus_writeb(cor1, &bp->r[chip]->r[CD180_COR1]);
+	sbus_writeb(port->COR2, &bp->r[chip]->r[CD180_COR2]);
+	sbus_writeb(cor3, &bp->r[chip]->r[CD180_COR3]);
+	/* Make CD180 know about registers change */
+	aurora_wait_CCR(bp->r[chip]);
+	sbus_writeb(CCR_CORCHG1 | CCR_CORCHG2 | CCR_CORCHG3,
+		    &bp->r[chip]->r[CD180_CCR]);
+	/* Setting up modem option registers */
+	sbus_writeb(mcor1, &bp->r[chip]->r[CD180_MCOR1]);
+	sbus_writeb(mcor2, &bp->r[chip]->r[CD180_MCOR2]);
+	/* Enable CD180 transmitter & receiver */
+	aurora_wait_CCR(bp->r[chip]);
+	sbus_writeb(CCR_TXEN | CCR_RXEN, &bp->r[chip]->r[CD180_CCR]);
+	/* Enable interrupts */
+	sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	/* And finally set RTS on */
+	sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+#ifdef AURORA_DEBUG
+	printk("aurora_change_speed: end\n");
+#endif
+}
+
+/* Must be called with interrupts enabled */
+static int aurora_setup_port(struct Aurora_board *bp, struct Aurora_port *port)
+{
+	unsigned long flags;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_setup_port: start %d\n",port_No(port));
+#endif
+	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);
+		
+#ifdef MODULE
+	if ((port->count == 1) && ((++bp->count) == 1))
+			bp->flags |= AURORA_BOARD_ACTIVE;
+#endif
+
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	aurora_change_speed(bp, port);
+	port->flags |= ASYNC_INITIALIZED;
+		
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_setup_port: end\n");
+#endif
+	return 0;
+}
+
+/* Must be called with interrupts disabled */
+static void aurora_shutdown_port(struct Aurora_board *bp, struct Aurora_port *port)
+{
+	struct tty_struct *tty;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_shutdown_port: start\n");
+#endif
+	if (!(port->flags & ASYNC_INITIALIZED)) 
+		return;
+	
+	chip = AURORA_CD180(port_No(port));
+	
+#ifdef AURORA_REPORT_OVERRUN
+	printk("aurora%d: port %d: Total %ld overruns were detected.\n",
+	       board_No(bp), port_No(port), port->overrun);
+#endif	
+#ifdef AURORA_REPORT_FIFO
+	{
+		int i;
+		
+		printk("aurora%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 */
+		port->MSVR &= ~(bp->DTR|bp->RTS);
+		sbus_writeb(port->MSVR,
+			    &bp->r[chip]->r[CD180_MSVR]);
+	}
+	
+        /* Select port */
+	sbus_writeb(port_No(port) & 7,
+		    &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+
+	/* Reset port */
+	aurora_wait_CCR(bp->r[chip]);
+	sbus_writeb(CCR_SOFTRESET, &bp->r[chip]->r[CD180_CCR]);
+
+	/* Disable all interrupts from this port */
+	port->SRER = 0;
+	sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	
+	if (tty)  
+		set_bit(TTY_IO_ERROR, &tty->flags);
+	port->flags &= ~ASYNC_INITIALIZED;
+
+#ifdef MODULE
+	if (--bp->count < 0)  {
+		printk(KERN_DEBUG "aurora%d: aurora_shutdown_port: "
+		       "bad board count: %d\n",
+		       board_No(bp), bp->count);
+		bp->count = 0;
+	}
+	
+	if (!bp->count)
+		bp->flags &= ~AURORA_BOARD_ACTIVE;
+#endif
+
+#ifdef AURORA_DEBUG
+	printk("aurora_shutdown_port: end\n");
+#endif
+}
+
+	
+static int block_til_ready(struct tty_struct *tty, struct file * filp,
+			   struct Aurora_port *port)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	struct Aurora_board *bp = port_Board(port);
+	int    retval;
+	int    do_clocal = 0;
+	int    CD;
+	unsigned char chip;
+	
+#ifdef AURORA_DEBUG
+	printk("block_til_ready: start\n");
+#endif
+	chip = AURORA_CD180(port_No(port));
+
+	/* 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();
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		CD = sbus_readb(&bp->r[chip]->r[CD180_MSVR]) & MSVR_CD;
+		port->MSVR=bp->RTS;
+
+		/* auto drops DTR */
+		sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+		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;
+#ifdef AURORA_DEBUG
+	printk("block_til_ready: end\n");
+#endif
+	return 0;
+}	
+
+static int aurora_open(struct tty_struct * tty, struct file * filp)
+{
+	int board;
+	int error;
+	struct Aurora_port * port;
+	struct Aurora_board * bp;
+	unsigned long flags;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_open: start\n");
+#endif
+	
+	board = AURORA_BOARD(tty->index);
+	if (board > AURORA_NBOARD ||
+	    !(aurora_board[board].flags & AURORA_BOARD_PRESENT)) {
+#ifdef AURORA_DEBUG
+		printk("aurora_open: error board %d present %d\n",
+		       board, aurora_board[board].flags & AURORA_BOARD_PRESENT);
+#endif
+		return -ENODEV;
+	}
+	
+	bp = &aurora_board[board];
+	port = aurora_port + board * AURORA_NPORT * AURORA_NCD180 + AURORA_PORT(tty->index);
+	if ((aurora_paranoia_check(port, tty->name, "aurora_open")) {
+#ifdef AURORA_DEBUG
+		printk("aurora_open: error paranoia check\n");
+#endif
+		return -ENODEV;
+	}
+	
+	port->count++;
+	tty->driver_data = port;
+	port->tty = tty;
+	
+	if ((error = aurora_setup_port(bp, port))) {
+#ifdef AURORA_DEBUG
+		printk("aurora_open: error aurora_setup_port ret %d\n",error);
+#endif
+		return error;
+	}
+
+	if ((error = block_til_ready(tty, filp, port))) {
+#ifdef AURORA_DEBUG
+		printk("aurora_open: error block_til_ready ret %d\n",error);
+#endif
+		return error;
+	}
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_open: end\n");
+#endif
+	return 0;
+}
+
+static void aurora_close(struct tty_struct * tty, struct file * filp)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	unsigned long flags;
+	unsigned long timeout;
+	unsigned char chip;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_close: start\n");
+#endif
+	
+	if (!port || (aurora_paranoia_check(port, tty->name, "close"))
+		return;
+	
+	chip = AURORA_CD180(port_No(port));
+
+	save_flags(flags); cli();
+	if (tty_hung_up_p(filp))  {
+		restore_flags(flags);
+		return;
+	}
+	
+	bp = port_Board(port);
+	if ((tty->count == 1) && (port->count != 1))  {
+		printk(KERN_DEBUG "aurora%d: aurora_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_DEBUG "aurora%d: aurora_close: bad port "
+		       "count for tty%d: %d\n",
+		       board_No(bp), port_No(port), port->count);
+		port->count = 0;
+	}
+	if (port->count)  {
+		restore_flags(flags);
+		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){
+#ifdef AURORA_DEBUG
+		printk("aurora_close: waiting to flush...\n");
+#endif
+		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->SRER &= ~SRER_RXD;
+	if (port->flags & ASYNC_INITIALIZED) {
+		port->SRER &= ~SRER_TXRDY;
+		port->SRER |= SRER_TXEMPTY;
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+		/*
+		 * 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->SRER & SRER_TXEMPTY)  {
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(port->timeout);
+			if (time_after(jiffies, timeout))
+				break;
+		}
+	}
+#ifdef AURORA_DEBUG
+	printk("aurora_close: shutdown_port\n");
+#endif
+	aurora_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 = 0;
+	if (port->blocked_open) {
+		if (port->close_delay) {
+			current->state = TASK_INTERRUPTIBLE;
+			schedule_timeout(port->close_delay);
+		}
+		wake_up_interruptible(&port->open_wait);
+	}
+	port->flags &= ~(ASYNC_NORMAL_ACTIVE|ASYNC_CLOSING);
+	wake_up_interruptible(&port->close_wait);
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_close: end\n");
+#endif
+}
+
+static int aurora_write(struct tty_struct * tty, 
+			const unsigned char *buf, int count)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	int c, total = 0;
+	unsigned long flags;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_write: start %d\n",count);
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_write"))
+		return 0;
+		
+	chip = AURORA_CD180(port_No(port));
+	
+	bp = port_Board(port);
+
+	if (!tty || !port->xmit_buf || !tmp_buf)
+		return 0;
+
+	save_flags(flags);
+	while (1) {
+		cli();
+		c = MIN(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->SRER & SRER_TXRDY)) {
+		port->SRER |= SRER_TXRDY;
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	}
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_write: end %d\n",total);
+#endif
+	return total;
+}
+
+static void aurora_put_char(struct tty_struct * tty, unsigned char ch)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	unsigned long flags;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_put_char: start %c\n",ch);
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_put_char"))
+		return;
+
+	if (!tty || !port->xmit_buf)
+		return;
+
+	save_flags(flags); cli();
+	
+	if (port->xmit_cnt >= SERIAL_XMIT_SIZE - 1) {
+		restore_flags(flags);
+		return;
+	}
+
+	port->xmit_buf[port->xmit_head++] = ch;
+	port->xmit_head &= SERIAL_XMIT_SIZE - 1;
+	port->xmit_cnt++;
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_put_char: end\n");
+#endif
+}
+
+static void aurora_flush_chars(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	unsigned long flags;
+	unsigned char chip;
+
+/*#ifdef AURORA_DEBUG
+	printk("aurora_flush_chars: start\n");
+#endif*/
+	if ((aurora_paranoia_check(port, tty->name, "aurora_flush_chars"))
+		return;
+		
+	chip = AURORA_CD180(port_No(port));
+	
+	if (port->xmit_cnt <= 0 || tty->stopped || tty->hw_stopped ||
+	    !port->xmit_buf)
+		return;
+
+	save_flags(flags); cli();
+	port->SRER |= SRER_TXRDY;
+	sbus_writeb(port_No(port) & 7,
+		    &port_Board(port)->r[chip]->r[CD180_CAR]);
+	udelay(1);
+	sbus_writeb(port->SRER,
+		    &port_Board(port)->r[chip]->r[CD180_SRER]);
+	restore_flags(flags);
+/*#ifdef AURORA_DEBUG
+	printk("aurora_flush_chars: end\n");
+#endif*/
+}
+
+static int aurora_write_room(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	int	ret;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_write_room: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_write_room"))
+		return 0;
+
+	ret = SERIAL_XMIT_SIZE - port->xmit_cnt - 1;
+	if (ret < 0)
+		ret = 0;
+#ifdef AURORA_DEBUG
+	printk("aurora_write_room: end\n");
+#endif
+	return ret;
+}
+
+static int aurora_chars_in_buffer(struct tty_struct *tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+				
+	if ((aurora_paranoia_check(port, tty->name, "aurora_chars_in_buffer"))
+		return 0;
+	
+	return port->xmit_cnt;
+}
+
+static void aurora_flush_buffer(struct tty_struct *tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	unsigned long flags;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_flush_buffer: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_flush_buffer"))
+		return;
+
+	save_flags(flags); cli();
+	port->xmit_cnt = port->xmit_head = port->xmit_tail = 0;
+	restore_flags(flags);
+	
+	tty_wakeup(tty);
+#ifdef AURORA_DEBUG
+	printk("aurora_flush_buffer: end\n");
+#endif
+}
+
+static int aurora_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board * bp;
+	unsigned char status,chip;
+	unsigned int result;
+	unsigned long flags;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_get_modem_info: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, __FUNCTION__))
+		return -ENODEV;
+
+	chip = AURORA_CD180(port_No(port));
+
+	bp = port_Board(port);
+
+	save_flags(flags); cli();
+
+	sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+
+	status = sbus_readb(&bp->r[chip]->r[CD180_MSVR]);
+	result = 0/*bp->r[chip]->r[AURORA_RI] & (1u << port_No(port)) ? 0 : TIOCM_RNG*/;
+
+	restore_flags(flags);
+
+	result |= ((status & bp->RTS) ? TIOCM_RTS : 0)
+		| ((status & bp->DTR) ? TIOCM_DTR : 0)
+		| ((status & MSVR_CD)  ? TIOCM_CAR : 0)
+		| ((status & MSVR_DSR) ? TIOCM_DSR : 0)
+		| ((status & MSVR_CTS) ? TIOCM_CTS : 0);
+
+#ifdef AURORA_DEBUG
+	printk("aurora_get_modem_info: end\n");
+#endif
+	return result;
+}
+
+static int aurora_tiocmset(struct tty_struct *tty, struct file *file,
+			   unsigned int set, unsigned int clear)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	unsigned int arg;
+	unsigned long flags;
+	struct Aurora_board *bp = port_Board(port);
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_set_modem_info: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, __FUNCTION__))
+		return -ENODEV;
+
+	chip = AURORA_CD180(port_No(port));
+
+	save_flags(flags); cli();
+	if (set & TIOCM_RTS)
+		port->MSVR |= bp->RTS;
+	if (set & TIOCM_DTR)
+		port->MSVR |= bp->DTR;
+	if (clear & TIOCM_RTS)
+		port->MSVR &= ~bp->RTS;
+	if (clear & TIOCM_DTR)
+		port->MSVR &= ~bp->DTR;
+
+	sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+
+	sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_set_modem_info: end\n");
+#endif
+	return 0;
+}
+
+static void aurora_send_break(struct Aurora_port * port, unsigned long length)
+{
+	struct Aurora_board *bp = port_Board(port);
+	unsigned long flags;
+	unsigned char chip;
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_send_break: start\n");
+#endif
+	chip = AURORA_CD180(port_No(port));
+	
+	save_flags(flags); cli();
+
+	port->break_length = AURORA_TPS / HZ * length;
+	port->COR2 |= COR2_ETC;
+	port->SRER  |= SRER_TXRDY;
+	sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+
+	sbus_writeb(port->COR2, &bp->r[chip]->r[CD180_COR2]);
+	sbus_writeb(port->SRER, &bp->r[chip]->r[CD180_SRER]);
+	aurora_wait_CCR(bp->r[chip]);
+
+	sbus_writeb(CCR_CORCHG2, &bp->r[chip]->r[CD180_CCR]);
+	aurora_wait_CCR(bp->r[chip]);
+
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_send_break: end\n");
+#endif
+}
+
+static int aurora_set_serial_info(struct Aurora_port * port,
+				  struct serial_struct * newinfo)
+{
+	struct serial_struct tmp;
+	struct Aurora_board *bp = port_Board(port);
+	int change_speed;
+	unsigned long flags;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_set_serial_info: start\n");
+#endif
+	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 != (bp->oscfreq + CD180_TPC/2) / CD180_TPC) ||
+	    (tmp.custom_divisor != 0) ||
+	    (tmp.xmit_fifo_size != CD180_NFIFO) ||
+	    (tmp.flags & ~AURORA_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();
+		aurora_change_speed(bp, port);
+		restore_flags(flags);
+	}
+#ifdef AURORA_DEBUG
+	printk("aurora_set_serial_info: end\n");
+#endif
+	return 0;
+}
+
+extern int aurora_get_serial_info(struct Aurora_port * port,
+				  struct serial_struct * retinfo)
+{
+	struct serial_struct tmp;
+	struct Aurora_board *bp = port_Board(port);
+	
+#ifdef AURORA_DEBUG
+	printk("aurora_get_serial_info: start\n");
+#endif
+	if (!access_ok(VERIFY_WRITE, (void *) retinfo, sizeof(tmp)))
+		return -EFAULT;
+	
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.type = PORT_CIRRUS;
+	tmp.line = port - aurora_port;
+	tmp.port = 0;
+	tmp.irq  = bp->irq;
+	tmp.flags = port->flags;
+	tmp.baud_base = (bp->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;
+	copy_to_user(retinfo, &tmp, sizeof(tmp));
+#ifdef AURORA_DEBUG
+printk("aurora_get_serial_info: end\n");
+#endif
+	return 0;
+}
+
+static int aurora_ioctl(struct tty_struct * tty, struct file * filp, 
+		    unsigned int cmd, unsigned long arg)
+		    
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	int retval;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_ioctl: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_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)
+			aurora_send_break(port, 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);
+		aurora_send_break(port, arg ? arg*(HZ/10) : HZ/4);
+		return 0;
+	case TIOCGSOFTCAR:
+		return put_user(C_CLOCAL(tty) ? 1 : 0, (unsigned long *)arg);
+	case TIOCSSOFTCAR:
+		if (get_user(arg,(unsigned long *)arg))
+			return -EFAULT;
+		tty->termios->c_cflag =
+			((tty->termios->c_cflag & ~CLOCAL) |
+			 (arg ? CLOCAL : 0));
+		return 0;
+	case TIOCGSERIAL:	
+		return aurora_get_serial_info(port, (struct serial_struct *) arg);
+	case TIOCSSERIAL:	
+		return aurora_set_serial_info(port, (struct serial_struct *) arg);
+	default:
+		return -ENOIOCTLCMD;
+	};
+#ifdef AURORA_DEBUG
+	printk("aurora_ioctl: end\n");
+#endif
+	return 0;
+}
+
+static void aurora_throttle(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	unsigned long flags;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_throttle: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_throttle"))
+		return;
+	
+	bp = port_Board(port);
+	chip = AURORA_CD180(port_No(port));
+	
+	save_flags(flags); cli();
+	port->MSVR &= ~bp->RTS;
+	sbus_writeb(port_No(port) & 7, &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+	if (I_IXOFF(tty))  {
+		aurora_wait_CCR(bp->r[chip]);
+		sbus_writeb(CCR_SSCH2, &bp->r[chip]->r[CD180_CCR]);
+		aurora_wait_CCR(bp->r[chip]);
+	}
+	sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_throttle: end\n");
+#endif
+}
+
+static void aurora_unthrottle(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	unsigned long flags;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_unthrottle: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_unthrottle"))
+		return;
+	
+	bp = port_Board(port);
+	
+	chip = AURORA_CD180(port_No(port));
+	
+	save_flags(flags); cli();
+	port->MSVR |= bp->RTS;
+	sbus_writeb(port_No(port) & 7,
+		    &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+	if (I_IXOFF(tty))  {
+		aurora_wait_CCR(bp->r[chip]);
+		sbus_writeb(CCR_SSCH1,
+			    &bp->r[chip]->r[CD180_CCR]);
+		aurora_wait_CCR(bp->r[chip]);
+	}
+	sbus_writeb(port->MSVR, &bp->r[chip]->r[CD180_MSVR]);
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_unthrottle: end\n");
+#endif
+}
+
+static void aurora_stop(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	unsigned long flags;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_stop: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_stop"))
+		return;
+	
+	bp = port_Board(port);
+	
+	chip = AURORA_CD180(port_No(port));
+	
+	save_flags(flags); cli();
+	port->SRER &= ~SRER_TXRDY;
+	sbus_writeb(port_No(port) & 7,
+		    &bp->r[chip]->r[CD180_CAR]);
+	udelay(1);
+	sbus_writeb(port->SRER,
+		    &bp->r[chip]->r[CD180_SRER]);
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_stop: end\n");
+#endif
+}
+
+static void aurora_start(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+	unsigned long flags;
+	unsigned char chip;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_start: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_start"))
+		return;
+	
+	bp = port_Board(port);
+	
+	chip = AURORA_CD180(port_No(port));
+	
+	save_flags(flags); cli();
+	if (port->xmit_cnt && port->xmit_buf && !(port->SRER & SRER_TXRDY))  {
+		port->SRER |= SRER_TXRDY;
+		sbus_writeb(port_No(port) & 7,
+			    &bp->r[chip]->r[CD180_CAR]);
+		udelay(1);
+		sbus_writeb(port->SRER,
+			    &bp->r[chip]->r[CD180_SRER]);
+	}
+	restore_flags(flags);
+#ifdef AURORA_DEBUG
+	printk("aurora_start: end\n");
+#endif
+}
+
+/*
+ * 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_aurora_hangup() -> tty->hangup() -> aurora_hangup()
+ * 
+ */
+static void do_aurora_hangup(void *private_)
+{
+	struct Aurora_port	*port = (struct Aurora_port *) private_;
+	struct tty_struct	*tty;
+
+#ifdef AURORA_DEBUG
+	printk("do_aurora_hangup: start\n");
+#endif
+	tty = port->tty;
+	if (tty != NULL) {
+		tty_hangup(tty);	/* FIXME: module removal race - AKPM */
+#ifdef AURORA_DEBUG
+		printk("do_aurora_hangup: end\n");
+#endif
+	}
+}
+
+static void aurora_hangup(struct tty_struct * tty)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	struct Aurora_board *bp;
+				
+#ifdef AURORA_DEBUG
+	printk("aurora_hangup: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_hangup"))
+		return;
+	
+	bp = port_Board(port);
+	
+	aurora_shutdown_port(bp, port);
+	port->event = 0;
+	port->count = 0;
+	port->flags &= ~ASYNC_NORMAL_ACTIVE;
+	port->tty = 0;
+	wake_up_interruptible(&port->open_wait);
+#ifdef AURORA_DEBUG
+	printk("aurora_hangup: end\n");
+#endif
+}
+
+static void aurora_set_termios(struct tty_struct * tty, struct termios * old_termios)
+{
+	struct Aurora_port *port = (struct Aurora_port *) tty->driver_data;
+	unsigned long flags;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_set_termios: start\n");
+#endif
+	if ((aurora_paranoia_check(port, tty->name, "aurora_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();
+	aurora_change_speed(port_Board(port), port);
+	restore_flags(flags);
+
+	if ((old_termios->c_cflag & CRTSCTS) &&
+	    !(tty->termios->c_cflag & CRTSCTS)) {
+		tty->hw_stopped = 0;
+		aurora_start(tty);
+	}
+#ifdef AURORA_DEBUG
+	printk("aurora_set_termios: end\n");
+#endif
+}
+
+static void do_aurora_bh(void)
+{
+	 run_task_queue(&tq_aurora);
+}
+
+static void do_softint(void *private_)
+{
+	struct Aurora_port	*port = (struct Aurora_port *) private_;
+	struct tty_struct	*tty;
+
+#ifdef AURORA_DEBUG
+	printk("do_softint: start\n");
+#endif
+	tty = port->tty;
+	if (tty == NULL)
+		return;
+
+	if (test_and_clear_bit(RS_EVENT_WRITE_WAKEUP, &port->event)) {
+		tty_wakeup(tty);
+	}
+#ifdef AURORA_DEBUG
+	printk("do_softint: end\n");
+#endif
+}
+
+static struct tty_operations aurora_ops = {
+	.open  = aurora_open,
+	.close = aurora_close,
+	.write = aurora_write,
+	.put_char = aurora_put_char,
+	.flush_chars = aurora_flush_chars,
+	.write_room = aurora_write_room,
+	.chars_in_buffer = aurora_chars_in_buffer,
+	.flush_buffer = aurora_flush_buffer,
+	.ioctl = aurora_ioctl,
+	.throttle = aurora_throttle,
+	.unthrottle = aurora_unthrottle,
+	.set_termios = aurora_set_termios,
+	.stop = aurora_stop,
+	.start = aurora_start,
+	.hangup = aurora_hangup,
+	.tiocmget = aurora_tiocmget,
+	.tiocmset = aurora_tiocmset,
+};
+
+static int aurora_init_drivers(void)
+{
+	int error;
+	int i;
+
+#ifdef AURORA_DEBUG
+	printk("aurora_init_drivers: start\n");
+#endif
+	tmp_buf = (unsigned char *) get_zeroed_page(GFP_KERNEL);
+	if (tmp_buf == NULL) {
+		printk(KERN_ERR "aurora: Couldn't get free page.\n");
+		return 1;
+	}
+	init_bh(AURORA_BH, do_aurora_bh);
+	aurora_driver = alloc_tty_driver(AURORA_INPORTS);
+	if (!aurora_driver) {
+		printk(KERN_ERR "aurora: Couldn't allocate tty driver.\n");
+		free_page((unsigned long) tmp_buf);
+		return 1;
+	}
+	aurora_driver->owner = THIS_MODULE;
+	aurora_driver->name = "ttyA";
+	aurora_driver->major = AURORA_MAJOR;
+	aurora_driver->type = TTY_DRIVER_TYPE_SERIAL;
+	aurora_driver->subtype = SERIAL_TYPE_NORMAL;
+	aurora_driver->init_termios = tty_std_termios;
+	aurora_driver->init_termios.c_cflag =
+		B9600 | CS8 | CREAD | HUPCL | CLOCAL;
+	aurora_driver->flags = TTY_DRIVER_REAL_RAW;
+	tty_set_operations(aurora_driver, &aurora_ops);
+	error = tty_register_driver(aurora_driver);
+	if (error) {
+		put_tty_driver(aurora_driver);
+		free_page((unsigned long) tmp_buf);
+		printk(KERN_ERR "aurora: Couldn't register aurora driver, error = %d\n",
+		       error);
+		return 1;
+	}
+	
+	memset(aurora_port, 0, sizeof(aurora_port));
+	for (i = 0; i < AURORA_TNPORTS; i++)  {
+		aurora_port[i].magic = AURORA_MAGIC;
+		aurora_port[i].tqueue.routine = do_softint;
+		aurora_port[i].tqueue.data = &aurora_port[i];
+		aurora_port[i].tqueue_hangup.routine = do_aurora_hangup;
+		aurora_port[i].tqueue_hangup.data = &aurora_port[i];
+		aurora_port[i].close_delay = 50 * HZ/100;
+		aurora_port[i].closing_wait = 3000 * HZ/100;
+		init_waitqueue_head(&aurora_port[i].open_wait);
+		init_waitqueue_head(&aurora_port[i].close_wait);
+	}
+#ifdef AURORA_DEBUG
+	printk("aurora_init_drivers: end\n");
+#endif
+	return 0;
+}
+
+static void aurora_release_drivers(void)
+{
+#ifdef AURORA_DEBUG
+	printk("aurora_release_drivers: start\n");
+#endif
+	free_page((unsigned long)tmp_buf);
+	tty_unregister_driver(aurora_driver);
+	put_tty_driver(aurora_driver);
+#ifdef AURORA_DEBUG
+	printk("aurora_release_drivers: end\n");
+#endif
+}
+
+/*
+ * 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.
+ *
+ */
+void __init aurora_setup(char *str, int *ints)
+{
+	int i;
+
+	for(i=0;(i<ints[0])&&(i<4);i++) {
+		if (ints[i+1]) irqs[i]=ints[i+1];
+		}
+}
+
+static int __init aurora_real_init(void)
+{
+	int found;
+	int i;
+
+	printk(KERN_INFO "aurora: Driver starting.\n");
+	if(aurora_init_drivers())
+		return -EIO;
+	found = aurora_probe();
+	if(!found) {
+		aurora_release_drivers();
+		printk(KERN_INFO "aurora: No Aurora Multiport boards detected.\n");
+		return -EIO;
+	} else {
+		printk(KERN_INFO "aurora: %d boards found.\n", found);
+	}
+	for (i = 0; i < found; i++) {
+		int ret = aurora_setup_board(&aurora_board[i]);
+
+		if (ret) {
+#ifdef AURORA_DEBUG
+			printk(KERN_ERR "aurora_init: error aurora_setup_board ret %d\n",
+			       ret);
+#endif
+			return ret;
+		}
+	}
+	return 0;
+}
+
+int irq  = 0;
+int irq1 = 0;
+int irq2 = 0;
+int irq3 = 0;
+module_param(irq , int, 0);
+module_param(irq1, int, 0);
+module_param(irq2, int, 0);
+module_param(irq3, int, 0);
+
+static int __init aurora_init(void) 
+{
+	if (irq ) irqs[0]=irq ;
+	if (irq1) irqs[1]=irq1;
+	if (irq2) irqs[2]=irq2;
+	if (irq3) irqs[3]=irq3;
+	return aurora_real_init();
+}
+	
+static void __exit aurora_cleanup(void)
+{
+	int i;
+	
+#ifdef AURORA_DEBUG
+printk("cleanup_module: aurora_release_drivers\n");
+#endif
+
+	aurora_release_drivers();
+	for (i = 0; i < AURORA_NBOARD; i++)
+		if (aurora_board[i].flags & AURORA_BOARD_PRESENT) {
+			aurora_shutdown_board(&aurora_board[i]);
+			aurora_release_io_range(&aurora_board[i]);
+		}
+}
+
+module_init(aurora_init);
+module_exit(aurora_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/aurora.h b/drivers/sbus/char/aurora.h
new file mode 100644
index 0000000..b8b5476d
--- /dev/null
+++ b/drivers/sbus/char/aurora.h
@@ -0,0 +1,276 @@
+/*	$Id: aurora.h,v 1.6 2001/06/05 12:23:38 davem Exp $
+ *	linux/drivers/sbus/char/aurora.h -- Aurora multiport driver
+ *
+ *	Copyright (c) 1999 by Oliver Aldulea (oli@bv.ro)
+ *
+ *	This code is based on the RISCom/8 multiport serial driver written
+ *	by Dmitry Gorodchanin (pgmdsg@ibi.com), based on the Linux serial
+ *	driver, written by Linus Torvalds, Theodore T'so and others.
+ *	The Aurora multiport programming info was obtained mainly from the
+ *	Cirrus Logic CD180 documentation (available on the web), and by
+ *	doing heavy tests on the board. Many thanks to Eddie C. Dost for the
+ *	help on the sbus interface.
+ *
+ *	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.0
+ *
+ *	This is the first public release.
+ *
+ *	This version needs a lot of feedback. This is the version that works
+ *	with _my_ board. My board is model 1600se, revision '@(#)1600se.fth
+ *	1.2 3/28/95 1'. The driver might work with your board, but I do not
+ *	guarantee it. If you have _any_ type of board, I need to know if the
+ *	driver works or not, I need to know exactly your board parameters
+ *	(get them with 'cd /proc/openprom/iommu/sbus/sio16/; ls *; cat *')
+ *	Also, I need your board revision code, which is written on the board.
+ *	Send me the output of my driver too (it outputs through klogd).
+ *
+ *	If the driver does not work, you can try enabling the debug options
+ *	to see what's wrong or what should be done.
+ *
+ *	I'm sorry about the alignment of the code. It was written in a
+ *	128x48 environment.
+ *
+ *	I must say that I do not like Aurora Technologies' policy. I asked
+ *	them to help me do this driver faster, but they ended by something
+ *	like "don't call us, we'll call you", and I never heard anything
+ *	from them. They told me "knowing the way the board works, I don't
+ *	doubt you and others on the net will make the driver."
+ *	The truth about this board is that it has nothing intelligent on it.
+ *	If you want to say to somebody what kind of board you have, say that
+ *	it uses Cirrus Logic processors (CD180). The power of the board is
+ *	in those two chips. The rest of the board is the interface to the
+ *	sbus and to the peripherals. Still, they did something smart: they
+ *	reversed DTR and RTS to make on-board automatic hardware flow
+ *	control usable.
+ *	Thanks to Aurora Technologies for wasting my time, nerves and money.
+ */
+
+#ifndef __LINUX_AURORA_H
+#define __LINUX_AURORA_H
+
+#include <linux/serial.h>
+#include <linux/serialP.h>
+
+#ifdef __KERNEL__
+
+/* This is the number of boards to support. I've only tested this driver with
+ * one board, so it might not work.
+ */
+#define AURORA_NBOARD 1
+
+/* Useful ? Yes. But you can safely comment the warnings if they annoy you
+ * (let me say that again: the warnings in the code, not this define). 
+ */
+#define AURORA_PARANOIA_CHECK
+
+/* Well, after many lost nights, I found that the IRQ for this board is
+ * selected from four built-in values by writing some bits in the
+ * configuration register. This causes a little problem to occur: which
+ * IRQ to select ? Which one is the best for the user ? Well, I finally
+ * decided for the following algorithm: if the "bintr" value is not acceptable
+ * (not within type_1_irq[], then test the "intr" value, if that fails too,
+ * try each value from type_1_irq until succeded. Hope it's ok.
+ * You can safely reorder the irq's.
+ */
+#define TYPE_1_IRQS 4
+unsigned char type_1_irq[TYPE_1_IRQS] = {
+	3, 5, 9, 13
+};
+/* I know something about another method of interrupt setting, but not enough.
+ * Also, this is for another type of board, so I first have to learn how to
+ * detect it.
+#define TYPE_2_IRQS 3
+unsigned char type_2_irq[TYPE_2_IRQS] = {
+	0, 0, 0 ** could anyone find these for me ? (see AURORA_ALLIRQ below) **
+	};
+unsigned char type_2_mask[TYPE_2_IRQS] = {
+	32, 64, 128
+	};
+*/
+
+/* The following section should only be modified by those who know what
+ * they're doing (or don't, but want to help with some feedback). Modifying
+ * anything raises a _big_ probability for your system to hang, but the
+ * sacrifice worths. (I sacrificed my ext2fs many, many times...)
+ */
+
+/* This one tries to dump to console the name of almost every function called,
+ * and many other debugging info.
+ */
+#undef AURORA_DEBUG
+
+/* These are the most dangerous and useful defines. They do printk() during
+ * the interrupt processing routine(s), so if you manage to get "flooded" by
+ * irq's, start thinking about the "Power off/on" button...
+ */
+#undef AURORA_INTNORM	/* This one enables the "normal" messages, but some
+			 * of them cause flood, so I preffered putting
+			 * them under a define */
+#undef AURORA_INT_DEBUG /* This one is really bad. */
+
+/* Here's something helpful: after n irq's, the board will be disabled. This
+ * prevents irq flooding during debug (no need to think about power
+ * off/on anymore...)
+ */
+#define AURORA_FLOODPRO	10
+
+/* This one helps finding which irq the board calls, in case of a strange/
+ * unsupported board. AURORA_INT_DEBUG should be enabled, because I don't
+ * think /proc/interrupts or any command will be available in case of an irq
+ * flood... "allirq" is the list of all free irq's.
+ */
+/*
+#define AURORA_ALLIRQ 6
+int allirq[AURORA_ALLIRQ]={
+	2,3,5,7,9,13
+	};
+*/
+
+/* These must not be modified. These values are assumed during the code for
+ * performance optimisations.
+ */
+#define AURORA_NCD180 2 /* two chips per board */
+#define AURORA_NPORT 8  /* 8 ports per chip */
+
+/* several utilities */
+#define AURORA_BOARD(line)	(((line) >> 4) & 0x01)
+#define AURORA_CD180(line)	(((line) >> 3) & 0x01)
+#define AURORA_PORT(line)	((line) & 15)
+
+#define AURORA_TNPORTS (AURORA_NBOARD*AURORA_NCD180*AURORA_NPORT)
+
+/* Ticks per sec. Used for setting receiver timeout and break length */
+#define AURORA_TPS		4000
+
+#define AURORA_MAGIC	0x0A18
+
+/* Yeah, after heavy testing I decided it must be 6.
+ * Sure, You can change it if needed.
+ */
+#define AURORA_RXFIFO		6	/* Max. receiver FIFO size (1-8) */
+
+#define AURORA_RXTH		7
+
+struct aurora_reg1 {
+	__volatile__ unsigned char r;
+};
+
+struct aurora_reg128 {
+	__volatile__ unsigned char r[128];
+};
+	
+struct aurora_reg4 {
+	__volatile__ unsigned char r[4];
+};
+
+struct Aurora_board {
+	unsigned long		flags;
+	struct aurora_reg1	* r0;	/* This is the board configuration
+					 * register (write-only). */
+	struct aurora_reg128	* r[2];	/* These are the registers for the
+					 * two chips. */
+	struct aurora_reg4	* r3;	/* These are used for hardware-based
+					 * acknowledge. Software-based ack is
+					 * not supported by CD180. */
+	unsigned int		oscfreq; /* The on-board oscillator
+					  * frequency, in Hz. */
+	unsigned char		irq;
+#ifdef MODULE
+	signed char		count;	/* counts the use of the board */
+#endif
+	/* Values for the dtr_rts swapped mode. */
+	unsigned char		DTR;
+	unsigned char		RTS;
+	unsigned char		MSVDTR;
+	unsigned char		MSVRTS;
+	/* Values for hardware acknowledge. */
+	unsigned char		ACK_MINT, ACK_TINT, ACK_RINT;
+};
+
+/* Board configuration register */
+#define AURORA_CFG_ENABLE_IO	8
+#define AURORA_CFG_ENABLE_IRQ	4
+
+/* Board flags */
+#define AURORA_BOARD_PRESENT		0x00000001
+#define AURORA_BOARD_ACTIVE		0x00000002
+#define AURORA_BOARD_TYPE_2		0x00000004	/* don't know how to
+							 * detect this yet */
+#define AURORA_BOARD_DTR_FLOW_OK	0x00000008
+
+/* The story goes like this: Cirrus programmed the CD-180 chip to do automatic
+ * hardware flow control, and do it using CTS and DTR. CTS is ok, but, if you
+ * have a modem and the chip drops DTR, then the modem will drop the carrier
+ * (ain't that cute...). Luckily, the guys at Aurora decided to swap DTR and
+ * RTS, which makes the flow control usable. I hope that all the boards made
+ * by Aurora have these two signals swapped. If your's doesn't but you have a
+ * breakout box, you can try to reverse them yourself, then set the following
+ * flag.
+ */
+#undef AURORA_FORCE_DTR_FLOW
+
+/* In fact, a few more words have to be said about hardware flow control.
+ * This driver handles "output" flow control through the on-board facility
+ * CTS Auto Enable. For the "input" flow control there are two cases when
+ * the flow should be controlled. The first case is when the kernel is so
+ * busy that it cannot process IRQ's in time; this flow control can only be
+ * activated by the on-board chip, and if the board has RTS and DTR swapped,
+ * this facility is usable. The second case is when the application is so
+ * busy that it cannot receive bytes from the kernel, and this flow must be
+ * activated by software. This second case is not yet implemented in this
+ * driver. Unfortunately, I estimate that the second case is the one that
+ * occurs the most.
+ */
+
+
+struct Aurora_port {
+	int			magic;
+	int			baud_base;
+	int			flags;
+	struct tty_struct 	* tty;
+	int			count;
+	int			blocked_open;
+	long			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 tq_struct	tqueue;
+	struct tq_struct	tqueue_hangup;
+	short			wakeup_chars;
+	short			break_length;
+	unsigned short		closing_wait;
+	unsigned char		mark_mask;
+	unsigned char		SRER;
+	unsigned char		MSVR;
+	unsigned char		COR2;
+#ifdef AURORA_REPORT_OVERRUN
+	unsigned long		overrun;
+#endif	
+#ifdef AURORA_REPORT_FIFO
+	unsigned long		hits[10];
+#endif
+};
+
+#endif
+#endif /*__LINUX_AURORA_H*/
+
diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c
new file mode 100644
index 0000000..d5259f7
--- /dev/null
+++ b/drivers/sbus/char/bbc_envctrl.c
@@ -0,0 +1,645 @@
+/* $Id: bbc_envctrl.c,v 1.4 2001/04/06 16:48:08 davem Exp $
+ * bbc_envctrl.c: UltraSPARC-III environment control driver.
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <asm/oplib.h>
+#include <asm/ebus.h>
+#define __KERNEL_SYSCALLS__
+static int errno;
+#include <asm/unistd.h>
+
+#include "bbc_i2c.h"
+#include "max1617.h"
+
+#undef ENVCTRL_TRACE
+
+/* WARNING: Making changes to this driver is very dangerous.
+ *          If you misprogram the sensor chips they can
+ *          cut the power on you instantly.
+ */
+
+/* Two temperature sensors exist in the SunBLADE-1000 enclosure.
+ * Both are implemented using max1617 i2c devices.  Each max1617
+ * monitors 2 temperatures, one for one of the cpu dies and the other
+ * for the ambient temperature.
+ *
+ * The max1617 is capable of being programmed with power-off
+ * temperature values, one low limit and one high limit.  These
+ * can be controlled independently for the cpu or ambient temperature.
+ * If a limit is violated, the power is simply shut off.  The frequency
+ * with which the max1617 does temperature sampling can be controlled
+ * as well.
+ *
+ * Three fans exist inside the machine, all three are controlled with
+ * an i2c digital to analog converter.  There is a fan directed at the
+ * two processor slots, another for the rest of the enclosure, and the
+ * third is for the power supply.  The first two fans may be speed
+ * controlled by changing the voltage fed to them.  The third fan may
+ * only be completely off or on.  The third fan is meant to only be
+ * disabled/enabled when entering/exiting the lowest power-saving
+ * mode of the machine.
+ *
+ * An environmental control kernel thread periodically monitors all
+ * temperature sensors.  Based upon the samples it will adjust the
+ * fan speeds to try and keep the system within a certain temperature
+ * range (the goal being to make the fans as quiet as possible without
+ * allowing the system to get too hot).
+ *
+ * If the temperature begins to rise/fall outside of the acceptable
+ * operating range, a periodic warning will be sent to the kernel log.
+ * The fans will be put on full blast to attempt to deal with this
+ * situation.  After exceeding the acceptable operating range by a
+ * certain threshold, the kernel thread will shut down the system.
+ * Here, the thread is attempting to shut the machine down cleanly
+ * before the hardware based power-off event is triggered.
+ */
+
+/* These settings are in Celsius.  We use these defaults only
+ * if we cannot interrogate the cpu-fru SEEPROM.
+ */
+struct temp_limits {
+	s8 high_pwroff, high_shutdown, high_warn;
+	s8 low_warn, low_shutdown, low_pwroff;
+};
+
+static struct temp_limits cpu_temp_limits[2] = {
+	{ 100, 85, 80, 5, -5, -10 },
+	{ 100, 85, 80, 5, -5, -10 },
+};
+
+static struct temp_limits amb_temp_limits[2] = {
+	{ 65, 55, 40, 5, -5, -10 },
+	{ 65, 55, 40, 5, -5, -10 },
+};
+
+enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX };
+
+struct bbc_cpu_temperature {
+	struct bbc_cpu_temperature	*next;
+
+	struct bbc_i2c_client		*client;
+	int				index;
+
+	/* Current readings, and history. */
+	s8				curr_cpu_temp;
+	s8				curr_amb_temp;
+	s8				prev_cpu_temp;
+	s8				prev_amb_temp;
+	s8				avg_cpu_temp;
+	s8				avg_amb_temp;
+
+	int				sample_tick;
+
+	enum fan_action			fan_todo[2];
+#define FAN_AMBIENT	0
+#define FAN_CPU		1
+};
+
+struct bbc_cpu_temperature *all_bbc_temps;
+
+struct bbc_fan_control {
+	struct bbc_fan_control 	*next;
+
+	struct bbc_i2c_client 	*client;
+	int 			index;
+
+	int			psupply_fan_on;
+	int			cpu_fan_speed;
+	int			system_fan_speed;
+};
+
+struct bbc_fan_control *all_bbc_fans;
+
+#define CPU_FAN_REG	0xf0
+#define SYS_FAN_REG	0xf2
+#define PSUPPLY_FAN_REG	0xf4
+
+#define FAN_SPEED_MIN	0x0c
+#define FAN_SPEED_MAX	0x3f
+
+#define PSUPPLY_FAN_ON	0x1f
+#define PSUPPLY_FAN_OFF	0x00
+
+static void set_fan_speeds(struct bbc_fan_control *fp)
+{
+	/* Put temperatures into range so we don't mis-program
+	 * the hardware.
+	 */
+	if (fp->cpu_fan_speed < FAN_SPEED_MIN)
+		fp->cpu_fan_speed = FAN_SPEED_MIN;
+	if (fp->cpu_fan_speed > FAN_SPEED_MAX)
+		fp->cpu_fan_speed = FAN_SPEED_MAX;
+	if (fp->system_fan_speed < FAN_SPEED_MIN)
+		fp->system_fan_speed = FAN_SPEED_MIN;
+	if (fp->system_fan_speed > FAN_SPEED_MAX)
+		fp->system_fan_speed = FAN_SPEED_MAX;
+#ifdef ENVCTRL_TRACE
+	printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n",
+	       fp->index,
+	       fp->cpu_fan_speed, fp->system_fan_speed);
+#endif
+
+	bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG);
+	bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG);
+	bbc_i2c_writeb(fp->client,
+		       (fp->psupply_fan_on ?
+			PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF),
+		       PSUPPLY_FAN_REG);
+}
+
+static void get_current_temps(struct bbc_cpu_temperature *tp)
+{
+	tp->prev_amb_temp = tp->curr_amb_temp;
+	bbc_i2c_readb(tp->client,
+		      (unsigned char *) &tp->curr_amb_temp,
+		      MAX1617_AMB_TEMP);
+	tp->prev_cpu_temp = tp->curr_cpu_temp;
+	bbc_i2c_readb(tp->client,
+		      (unsigned char *) &tp->curr_cpu_temp,
+		      MAX1617_CPU_TEMP);
+#ifdef ENVCTRL_TRACE
+	printk("temp%d: cpu(%d C) amb(%d C)\n",
+	       tp->index,
+	       (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp);
+#endif
+}
+
+
+static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp)
+{
+	static int shutting_down = 0;
+	static char *envp[] = { "HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+	char *argv[] = { "/sbin/shutdown", "-h", "now", NULL };
+	char *type = "???";
+	s8 val = -1;
+
+	if (shutting_down != 0)
+		return;
+
+	if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
+	    tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
+		type = "ambient";
+		val = tp->curr_amb_temp;
+	} else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
+		   tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
+		type = "CPU";
+		val = tp->curr_cpu_temp;
+	}
+
+	printk(KERN_CRIT "temp%d: Outside of safe %s "
+	       "operating temperature, %d C.\n",
+	       tp->index, type, val);
+
+	printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n");
+
+	shutting_down = 1;
+	if (execve("/sbin/shutdown", argv, envp) < 0)
+		printk(KERN_CRIT "envctrl: shutdown execution failed\n");
+}
+
+#define WARN_INTERVAL	(30 * HZ)
+
+static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
+{
+	int ret = 0;
+
+	if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
+		if (tp->curr_amb_temp >=
+		    amb_temp_limits[tp->index].high_warn) {
+			printk(KERN_WARNING "temp%d: "
+			       "Above safe ambient operating temperature, %d C.\n",
+			       tp->index, (int) tp->curr_amb_temp);
+			ret = 1;
+		} else if (tp->curr_amb_temp <
+			   amb_temp_limits[tp->index].low_warn) {
+			printk(KERN_WARNING "temp%d: "
+			       "Below safe ambient operating temperature, %d C.\n",
+			       tp->index, (int) tp->curr_amb_temp);
+			ret = 1;
+		}
+		if (ret)
+			*last_warn = jiffies;
+	} else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn ||
+		   tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn)
+		ret = 1;
+
+	/* Now check the shutdown limits. */
+	if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown ||
+	    tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) {
+		do_envctrl_shutdown(tp);
+		ret = 1;
+	}
+
+	if (ret) {
+		tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST;
+	} else if ((tick & (8 - 1)) == 0) {
+		s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10;
+		s8 amb_goal_lo;
+
+		amb_goal_lo = amb_goal_hi - 3;
+
+		/* We do not try to avoid 'too cold' events.  Basically we
+		 * only try to deal with over-heating and fan noise reduction.
+		 */
+		if (tp->avg_amb_temp < amb_goal_hi) {
+			if (tp->avg_amb_temp >= amb_goal_lo)
+				tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+			else
+				tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER;
+		} else {
+			tp->fan_todo[FAN_AMBIENT] = FAN_FASTER;
+		}
+	} else {
+		tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+	}
+}
+
+static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick)
+{
+	int ret = 0;
+
+	if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) {
+		if (tp->curr_cpu_temp >=
+		    cpu_temp_limits[tp->index].high_warn) {
+			printk(KERN_WARNING "temp%d: "
+			       "Above safe CPU operating temperature, %d C.\n",
+			       tp->index, (int) tp->curr_cpu_temp);
+			ret = 1;
+		} else if (tp->curr_cpu_temp <
+			   cpu_temp_limits[tp->index].low_warn) {
+			printk(KERN_WARNING "temp%d: "
+			       "Below safe CPU operating temperature, %d C.\n",
+			       tp->index, (int) tp->curr_cpu_temp);
+			ret = 1;
+		}
+		if (ret)
+			*last_warn = jiffies;
+	} else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn ||
+		   tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn)
+		ret = 1;
+
+	/* Now check the shutdown limits. */
+	if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown ||
+	    tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) {
+		do_envctrl_shutdown(tp);
+		ret = 1;
+	}
+
+	if (ret) {
+		tp->fan_todo[FAN_CPU] = FAN_FULLBLAST;
+	} else if ((tick & (8 - 1)) == 0) {
+		s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10;
+		s8 cpu_goal_lo;
+
+		cpu_goal_lo = cpu_goal_hi - 3;
+
+		/* We do not try to avoid 'too cold' events.  Basically we
+		 * only try to deal with over-heating and fan noise reduction.
+		 */
+		if (tp->avg_cpu_temp < cpu_goal_hi) {
+			if (tp->avg_cpu_temp >= cpu_goal_lo)
+				tp->fan_todo[FAN_CPU] = FAN_SAME;
+			else
+				tp->fan_todo[FAN_CPU] = FAN_SLOWER;
+		} else {
+			tp->fan_todo[FAN_CPU] = FAN_FASTER;
+		}
+	} else {
+		tp->fan_todo[FAN_CPU] = FAN_SAME;
+	}
+}
+
+static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn)
+{
+	tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2);
+	tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2);
+
+	analyze_ambient_temp(tp, last_warn, tp->sample_tick);
+	analyze_cpu_temp(tp, last_warn, tp->sample_tick);
+
+	tp->sample_tick++;
+}
+
+static enum fan_action prioritize_fan_action(int which_fan)
+{
+	struct bbc_cpu_temperature *tp;
+	enum fan_action decision = FAN_STATE_MAX;
+
+	/* Basically, prioritize what the temperature sensors
+	 * recommend we do, and perform that action on all the
+	 * fans.
+	 */
+	for (tp = all_bbc_temps; tp; tp = tp->next) {
+		if (tp->fan_todo[which_fan] == FAN_FULLBLAST) {
+			decision = FAN_FULLBLAST;
+			break;
+		}
+		if (tp->fan_todo[which_fan] == FAN_SAME &&
+		    decision != FAN_FASTER)
+			decision = FAN_SAME;
+		else if (tp->fan_todo[which_fan] == FAN_FASTER)
+			decision = FAN_FASTER;
+		else if (decision != FAN_FASTER &&
+			 decision != FAN_SAME &&
+			 tp->fan_todo[which_fan] == FAN_SLOWER)
+			decision = FAN_SLOWER;
+	}
+	if (decision == FAN_STATE_MAX)
+		decision = FAN_SAME;
+
+	return decision;
+}
+
+static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp)
+{
+	enum fan_action decision = prioritize_fan_action(FAN_AMBIENT);
+	int ret;
+
+	if (decision == FAN_SAME)
+		return 0;
+
+	ret = 1;
+	if (decision == FAN_FULLBLAST) {
+		if (fp->system_fan_speed >= FAN_SPEED_MAX)
+			ret = 0;
+		else
+			fp->system_fan_speed = FAN_SPEED_MAX;
+	} else {
+		if (decision == FAN_FASTER) {
+			if (fp->system_fan_speed >= FAN_SPEED_MAX)
+				ret = 0;
+			else
+				fp->system_fan_speed += 2;
+		} else {
+			int orig_speed = fp->system_fan_speed;
+
+			if (orig_speed <= FAN_SPEED_MIN ||
+			    orig_speed <= (fp->cpu_fan_speed - 3))
+				ret = 0;
+			else
+				fp->system_fan_speed -= 1;
+		}
+	}
+
+	return ret;
+}
+
+static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp)
+{
+	enum fan_action decision = prioritize_fan_action(FAN_CPU);
+	int ret;
+
+	if (decision == FAN_SAME)
+		return 0;
+
+	ret = 1;
+	if (decision == FAN_FULLBLAST) {
+		if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
+			ret = 0;
+		else
+			fp->cpu_fan_speed = FAN_SPEED_MAX;
+	} else {
+		if (decision == FAN_FASTER) {
+			if (fp->cpu_fan_speed >= FAN_SPEED_MAX)
+				ret = 0;
+			else {
+				fp->cpu_fan_speed += 2;
+				if (fp->system_fan_speed <
+				    (fp->cpu_fan_speed - 3))
+					fp->system_fan_speed =
+						fp->cpu_fan_speed - 3;
+			}
+		} else {
+			if (fp->cpu_fan_speed <= FAN_SPEED_MIN)
+				ret = 0;
+			else
+				fp->cpu_fan_speed -= 1;
+		}
+	}
+
+	return ret;
+}
+
+static void maybe_new_fan_speeds(struct bbc_fan_control *fp)
+{
+	int new;
+
+	new  = maybe_new_ambient_fan_speed(fp);
+	new |= maybe_new_cpu_fan_speed(fp);
+
+	if (new)
+		set_fan_speeds(fp);
+}
+
+static void fans_full_blast(void)
+{
+	struct bbc_fan_control *fp;
+
+	/* Since we will not be monitoring things anymore, put
+	 * the fans on full blast.
+	 */
+	for (fp = all_bbc_fans; fp; fp = fp->next) {
+		fp->cpu_fan_speed = FAN_SPEED_MAX;
+		fp->system_fan_speed = FAN_SPEED_MAX;
+		fp->psupply_fan_on = 1;
+		set_fan_speeds(fp);
+	}
+}
+
+#define POLL_INTERVAL	(5 * 1000)
+static unsigned long last_warning_jiffies;
+static struct task_struct *kenvctrld_task;
+
+static int kenvctrld(void *__unused)
+{
+	daemonize("kenvctrld");
+	allow_signal(SIGKILL);
+	kenvctrld_task = current;
+
+	printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n");
+	last_warning_jiffies = jiffies - WARN_INTERVAL;
+	for (;;) {
+		struct bbc_cpu_temperature *tp;
+		struct bbc_fan_control *fp;
+
+		msleep_interruptible(POLL_INTERVAL);
+		if (signal_pending(current))
+			break;
+
+		for (tp = all_bbc_temps; tp; tp = tp->next) {
+			get_current_temps(tp);
+			analyze_temps(tp, &last_warning_jiffies);
+		}
+		for (fp = all_bbc_fans; fp; fp = fp->next)
+			maybe_new_fan_speeds(fp);
+	}
+	printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n");
+
+	fans_full_blast();
+
+	return 0;
+}
+
+static void attach_one_temp(struct linux_ebus_child *echild, int temp_idx)
+{
+	struct bbc_cpu_temperature *tp = kmalloc(sizeof(*tp), GFP_KERNEL);
+
+	if (!tp)
+		return;
+	memset(tp, 0, sizeof(*tp));
+	tp->client = bbc_i2c_attach(echild);
+	if (!tp->client) {
+		kfree(tp);
+		return;
+	}
+
+	tp->index = temp_idx;
+	{
+		struct bbc_cpu_temperature **tpp = &all_bbc_temps;
+		while (*tpp)
+			tpp = &((*tpp)->next);
+		tp->next = NULL;
+		*tpp = tp;
+	}
+
+	/* Tell it to convert once every 5 seconds, clear all cfg
+	 * bits.
+	 */
+	bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE);
+	bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE);
+
+	/* Program the hard temperature limits into the chip. */
+	bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff,
+		       MAX1617_WR_AMB_HIGHLIM);
+	bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff,
+		       MAX1617_WR_AMB_LOWLIM);
+	bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff,
+		       MAX1617_WR_CPU_HIGHLIM);
+	bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff,
+		       MAX1617_WR_CPU_LOWLIM);
+
+	get_current_temps(tp);
+	tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp;
+	tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp;
+
+	tp->fan_todo[FAN_AMBIENT] = FAN_SAME;
+	tp->fan_todo[FAN_CPU] = FAN_SAME;
+}
+
+static void attach_one_fan(struct linux_ebus_child *echild, int fan_idx)
+{
+	struct bbc_fan_control *fp = kmalloc(sizeof(*fp), GFP_KERNEL);
+
+	if (!fp)
+		return;
+	memset(fp, 0, sizeof(*fp));
+	fp->client = bbc_i2c_attach(echild);
+	if (!fp->client) {
+		kfree(fp);
+		return;
+	}
+
+	fp->index = fan_idx;
+
+	{
+		struct bbc_fan_control **fpp = &all_bbc_fans;
+		while (*fpp)
+			fpp = &((*fpp)->next);
+		fp->next = NULL;
+		*fpp = fp;
+	}
+
+	/* The i2c device controlling the fans is write-only.
+	 * So the only way to keep track of the current power
+	 * level fed to the fans is via software.  Choose half
+	 * power for cpu/system and 'on' fo the powersupply fan
+	 * and set it now.
+	 */
+	fp->psupply_fan_on = 1;
+	fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
+	fp->cpu_fan_speed += FAN_SPEED_MIN;
+	fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2;
+	fp->system_fan_speed += FAN_SPEED_MIN;
+
+	set_fan_speeds(fp);
+}
+
+int bbc_envctrl_init(void)
+{
+	struct linux_ebus_child *echild;
+	int temp_index = 0;
+	int fan_index = 0;
+	int devidx = 0;
+	int err = 0;
+
+	while ((echild = bbc_i2c_getdev(devidx++)) != NULL) {
+		if (!strcmp(echild->prom_name, "temperature"))
+			attach_one_temp(echild, temp_index++);
+		if (!strcmp(echild->prom_name, "fan-control"))
+			attach_one_fan(echild, fan_index++);
+	}
+	if (temp_index != 0 && fan_index != 0)
+		err = kernel_thread(kenvctrld, NULL, CLONE_FS | CLONE_FILES);
+	return err;
+}
+
+static void destroy_one_temp(struct bbc_cpu_temperature *tp)
+{
+	bbc_i2c_detach(tp->client);
+	kfree(tp);
+}
+
+static void destroy_one_fan(struct bbc_fan_control *fp)
+{
+	bbc_i2c_detach(fp->client);
+	kfree(fp);
+}
+
+void bbc_envctrl_cleanup(void)
+{
+	struct bbc_cpu_temperature *tp;
+	struct bbc_fan_control *fp;
+
+	if (kenvctrld_task != NULL) {
+		force_sig(SIGKILL, kenvctrld_task);
+		for (;;) {
+			struct task_struct *p;
+			int found = 0;
+
+			read_lock(&tasklist_lock);
+			for_each_process(p) {
+				if (p == kenvctrld_task) {
+					found = 1;
+					break;
+				}
+			}
+			read_unlock(&tasklist_lock);
+			if (!found)
+				break;
+			msleep(1000);
+		}
+		kenvctrld_task = NULL;
+	}
+
+	tp = all_bbc_temps;
+	while (tp != NULL) {
+		struct bbc_cpu_temperature *next = tp->next;
+		destroy_one_temp(tp);
+		tp = next;
+	}
+	all_bbc_temps = NULL;
+
+	fp = all_bbc_fans;
+	while (fp != NULL) {
+		struct bbc_fan_control *next = fp->next;
+		destroy_one_fan(fp);
+		fp = next;
+	}
+	all_bbc_fans = NULL;
+}
diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c
new file mode 100644
index 0000000..1c8b612
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.c
@@ -0,0 +1,488 @@
+/* $Id: bbc_i2c.c,v 1.2 2001/04/02 09:59:08 davem Exp $
+ * bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III
+ *            platforms.
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <asm/oplib.h>
+#include <asm/ebus.h>
+#include <asm/spitfire.h>
+#include <asm/bbc.h>
+
+#include "bbc_i2c.h"
+
+/* Convert this driver to use i2c bus layer someday... */
+#define I2C_PCF_PIN	0x80
+#define I2C_PCF_ESO	0x40
+#define I2C_PCF_ES1	0x20
+#define I2C_PCF_ES2	0x10
+#define I2C_PCF_ENI	0x08
+#define I2C_PCF_STA	0x04
+#define I2C_PCF_STO	0x02
+#define I2C_PCF_ACK	0x01
+
+#define I2C_PCF_START    (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK)
+#define I2C_PCF_STOP     (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK)
+#define I2C_PCF_REPSTART (              I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK)
+#define I2C_PCF_IDLE     (I2C_PCF_PIN | I2C_PCF_ESO               | I2C_PCF_ACK)
+
+#define I2C_PCF_INI 0x40   /* 1 if not initialized */
+#define I2C_PCF_STS 0x20
+#define I2C_PCF_BER 0x10
+#define I2C_PCF_AD0 0x08
+#define I2C_PCF_LRB 0x08
+#define I2C_PCF_AAS 0x04
+#define I2C_PCF_LAB 0x02
+#define I2C_PCF_BB  0x01
+
+/* The BBC devices have two I2C controllers.  The first I2C controller
+ * connects mainly to configuration proms (NVRAM, cpu configuration,
+ * dimm types, etc.).  Whereas the second I2C controller connects to
+ * environmental control devices such as fans and temperature sensors.
+ * The second controller also connects to the smartcard reader, if present.
+ */
+
+#define NUM_CHILDREN	8
+struct bbc_i2c_bus {
+	struct bbc_i2c_bus		*next;
+	int				index;
+	spinlock_t			lock;
+	void				__iomem *i2c_bussel_reg;
+	void				__iomem *i2c_control_regs;
+	unsigned char			own, clock;
+
+	wait_queue_head_t		wq;
+	volatile int			waiting;
+
+	struct linux_ebus_device	*bus_edev;
+	struct {
+		struct linux_ebus_child	*device;
+		int			client_claimed;
+	} devs[NUM_CHILDREN];
+};
+
+static struct bbc_i2c_bus *all_bbc_i2c;
+
+struct bbc_i2c_client {
+	struct bbc_i2c_bus	*bp;
+	struct linux_ebus_child	*echild;
+	int			bus;
+	int			address;
+};
+
+static int find_device(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild)
+{
+	int i;
+
+	for (i = 0; i < NUM_CHILDREN; i++) {
+		if (bp->devs[i].device == echild) {
+			if (bp->devs[i].client_claimed)
+				return 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+static void set_device_claimage(struct bbc_i2c_bus *bp, struct linux_ebus_child *echild, int val)
+{
+	int i;
+
+	for (i = 0; i < NUM_CHILDREN; i++) {
+		if (bp->devs[i].device == echild) {
+			bp->devs[i].client_claimed = val;
+			return;
+		}
+	}
+}
+
+#define claim_device(BP,ECHILD)		set_device_claimage(BP,ECHILD,1)
+#define release_device(BP,ECHILD)	set_device_claimage(BP,ECHILD,0)
+
+static struct bbc_i2c_bus *find_bus_for_device(struct linux_ebus_child *echild)
+{
+	struct bbc_i2c_bus *bp = all_bbc_i2c;
+
+	while (bp != NULL) {
+		if (find_device(bp, echild) != 0)
+			break;
+		bp = bp->next;
+	}
+
+	return bp;
+}
+
+struct linux_ebus_child *bbc_i2c_getdev(int index)
+{
+	struct bbc_i2c_bus *bp = all_bbc_i2c;
+	struct linux_ebus_child *echild = NULL;
+	int curidx = 0;
+
+	while (bp != NULL) {
+		struct bbc_i2c_bus *next = bp->next;
+		int i;
+
+		for (i = 0; i < NUM_CHILDREN; i++) {
+			if (!(echild = bp->devs[i].device))
+				break;
+			if (curidx == index)
+				goto out;
+			echild = NULL;
+			curidx++;
+		}
+		bp = next;
+	}
+out:
+	if (curidx == index)
+		return echild;
+	return NULL;
+}
+
+struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *echild)
+{
+	struct bbc_i2c_bus *bp = find_bus_for_device(echild);
+	struct bbc_i2c_client *client;
+
+	if (!bp)
+		return NULL;
+	client = kmalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return NULL;
+	memset(client, 0, sizeof(*client));
+	client->bp = bp;
+	client->echild = echild;
+	client->bus = echild->resource[0].start;
+	client->address = echild->resource[1].start;
+
+	claim_device(bp, echild);
+
+	return client;
+}
+
+void bbc_i2c_detach(struct bbc_i2c_client *client)
+{
+	struct bbc_i2c_bus *bp = client->bp;
+	struct linux_ebus_child *echild = client->echild;
+
+	release_device(bp, echild);
+	kfree(client);
+}
+
+static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status)
+{
+	DECLARE_WAITQUEUE(wait, current);
+	int limit = 32;
+	int ret = 1;
+
+	bp->waiting = 1;
+	add_wait_queue(&bp->wq, &wait);
+	while (limit-- > 0) {
+		u8 val;
+
+		set_current_state(TASK_INTERRUPTIBLE);
+		*status = val = readb(bp->i2c_control_regs + 0);
+		if ((val & I2C_PCF_PIN) == 0) {
+			ret = 0;
+			break;
+		}
+		msleep_interruptible(250);
+	}
+	remove_wait_queue(&bp->wq, &wait);
+	bp->waiting = 0;
+	current->state = TASK_RUNNING;
+
+	return ret;
+}
+
+int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off)
+{
+	struct bbc_i2c_bus *bp = client->bp;
+	int address = client->address;
+	u8 status;
+	int ret = -1;
+
+	if (bp->i2c_bussel_reg != NULL)
+		writeb(client->bus, bp->i2c_bussel_reg);
+
+	writeb(address, bp->i2c_control_regs + 0x1);
+	writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	writeb(off, bp->i2c_control_regs + 0x1);
+	if (wait_for_pin(bp, &status) ||
+	    (status & I2C_PCF_LRB) != 0)
+		goto out;
+
+	writeb(val, bp->i2c_control_regs + 0x1);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	ret = 0;
+
+out:
+	writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+	return ret;
+}
+
+int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off)
+{
+	struct bbc_i2c_bus *bp = client->bp;
+	unsigned char address = client->address, status;
+	int ret = -1;
+
+	if (bp->i2c_bussel_reg != NULL)
+		writeb(client->bus, bp->i2c_bussel_reg);
+
+	writeb(address, bp->i2c_control_regs + 0x1);
+	writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	writeb(off, bp->i2c_control_regs + 0x1);
+	if (wait_for_pin(bp, &status) ||
+	    (status & I2C_PCF_LRB) != 0)
+		goto out;
+
+	writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+
+	address |= 0x1; /* READ */
+
+	writeb(address, bp->i2c_control_regs + 0x1);
+	writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	/* Set PIN back to one so the device sends the first
+	 * byte.
+	 */
+	(void) readb(bp->i2c_control_regs + 0x1);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0);
+	*byte = readb(bp->i2c_control_regs + 0x1);
+	if (wait_for_pin(bp, &status))
+		goto out;
+
+	ret = 0;
+
+out:
+	writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0);
+	(void) readb(bp->i2c_control_regs + 0x1);
+
+	return ret;
+}
+
+int bbc_i2c_write_buf(struct bbc_i2c_client *client,
+		      char *buf, int len, int off)
+{
+	int ret = 0;
+
+	while (len > 0) {
+		int err = bbc_i2c_writeb(client, *buf, off);
+
+		if (err < 0) {
+			ret = err;
+			break;
+		}
+
+		len--;
+		buf++;
+		off++;
+	}
+	return ret;
+}
+
+int bbc_i2c_read_buf(struct bbc_i2c_client *client,
+		     char *buf, int len, int off)
+{
+	int ret = 0;
+
+	while (len > 0) {
+		int err = bbc_i2c_readb(client, buf, off);
+		if (err < 0) {
+			ret = err;
+			break;
+		}
+		len--;
+		buf++;
+		off++;
+	}
+
+	return ret;
+}
+
+EXPORT_SYMBOL(bbc_i2c_getdev);
+EXPORT_SYMBOL(bbc_i2c_attach);
+EXPORT_SYMBOL(bbc_i2c_detach);
+EXPORT_SYMBOL(bbc_i2c_writeb);
+EXPORT_SYMBOL(bbc_i2c_readb);
+EXPORT_SYMBOL(bbc_i2c_write_buf);
+EXPORT_SYMBOL(bbc_i2c_read_buf);
+
+static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct bbc_i2c_bus *bp = dev_id;
+
+	/* PIN going from set to clear is the only event which
+	 * makes the i2c assert an interrupt.
+	 */
+	if (bp->waiting &&
+	    !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN))
+		wake_up(&bp->wq);
+
+	return IRQ_HANDLED;
+}
+
+static void __init reset_one_i2c(struct bbc_i2c_bus *bp)
+{
+	writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0);
+	writeb(bp->own, bp->i2c_control_regs + 0x1);
+	writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0);
+	writeb(bp->clock, bp->i2c_control_regs + 0x1);
+	writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0);
+}
+
+static int __init attach_one_i2c(struct linux_ebus_device *edev, int index)
+{
+	struct bbc_i2c_bus *bp = kmalloc(sizeof(*bp), GFP_KERNEL);
+	struct linux_ebus_child *echild;
+	int entry;
+
+	if (!bp)
+		return -ENOMEM;
+	memset(bp, 0, sizeof(*bp));
+
+	bp->i2c_control_regs = ioremap(edev->resource[0].start, 0x2);
+	if (!bp->i2c_control_regs)
+		goto fail;
+
+	if (edev->num_addrs == 2) {
+		bp->i2c_bussel_reg = ioremap(edev->resource[1].start, 0x1);
+		if (!bp->i2c_bussel_reg)
+			goto fail;
+	}
+
+	bp->waiting = 0;
+	init_waitqueue_head(&bp->wq);
+	if (request_irq(edev->irqs[0], bbc_i2c_interrupt,
+			SA_SHIRQ, "bbc_i2c", bp))
+		goto fail;
+
+	bp->index = index;
+	bp->bus_edev = edev;
+
+	spin_lock_init(&bp->lock);
+	bp->next = all_bbc_i2c;
+	all_bbc_i2c = bp;
+
+	entry = 0;
+	for (echild = edev->children;
+	     echild && entry < 8;
+	     echild = echild->next, entry++) {
+		bp->devs[entry].device = echild;
+		bp->devs[entry].client_claimed = 0;
+	}
+
+	writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0);
+	bp->own = readb(bp->i2c_control_regs + 0x01);
+	writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0);
+	bp->clock = readb(bp->i2c_control_regs + 0x01);
+
+	printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n",
+	       bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock);
+
+	reset_one_i2c(bp);
+
+	return 0;
+
+fail:
+	if (bp->i2c_bussel_reg)
+		iounmap(bp->i2c_bussel_reg);
+	if (bp->i2c_control_regs)
+		iounmap(bp->i2c_control_regs);
+	kfree(bp);
+	return -EINVAL;
+}
+
+static int __init bbc_present(void)
+{
+	struct linux_ebus *ebus = NULL;
+	struct linux_ebus_device *edev = NULL;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, "bbc"))
+				return 1;
+		}
+	}
+	return 0;
+}
+
+extern int bbc_envctrl_init(void);
+extern void bbc_envctrl_cleanup(void);
+static void bbc_i2c_cleanup(void);
+
+static int __init bbc_i2c_init(void)
+{
+	struct linux_ebus *ebus = NULL;
+	struct linux_ebus_device *edev = NULL;
+	int err, index = 0;
+
+	if (tlb_type != cheetah || !bbc_present())
+		return -ENODEV;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, "i2c")) {
+				if (!attach_one_i2c(edev, index))
+					index++;
+			}
+		}
+	}
+
+	if (!index)
+		return -ENODEV;
+
+	err = bbc_envctrl_init();
+	if (err)
+		bbc_i2c_cleanup();
+	return err;
+}
+
+static void bbc_i2c_cleanup(void)
+{
+	struct bbc_i2c_bus *bp = all_bbc_i2c;
+
+	bbc_envctrl_cleanup();
+
+	while (bp != NULL) {
+		struct bbc_i2c_bus *next = bp->next;
+
+		free_irq(bp->bus_edev->irqs[0], bp);
+
+		if (bp->i2c_bussel_reg)
+			iounmap(bp->i2c_bussel_reg);
+		if (bp->i2c_control_regs)
+			iounmap(bp->i2c_control_regs);
+
+		kfree(bp);
+
+		bp = next;
+	}
+	all_bbc_i2c = NULL;
+}
+
+module_init(bbc_i2c_init);
+module_exit(bbc_i2c_cleanup);
diff --git a/drivers/sbus/char/bbc_i2c.h b/drivers/sbus/char/bbc_i2c.h
new file mode 100644
index 0000000..fb01bd1
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.h
@@ -0,0 +1,20 @@
+/* $Id: bbc_i2c.h,v 1.2 2001/04/02 09:59:25 davem Exp $ */
+#ifndef _BBC_I2C_H
+#define _BBC_I2C_H
+
+#include <asm/ebus.h>
+
+struct bbc_i2c_client;
+
+/* Probing and attachment. */
+extern struct linux_ebus_child *bbc_i2c_getdev(int);
+extern struct bbc_i2c_client *bbc_i2c_attach(struct linux_ebus_child *);
+extern void bbc_i2c_detach(struct bbc_i2c_client *);
+
+/* Register read/write.  NOTE: Blocking! */
+extern int bbc_i2c_writeb(struct bbc_i2c_client *, unsigned char val, int off);
+extern int bbc_i2c_readb(struct bbc_i2c_client *, unsigned char *byte, int off);
+extern int bbc_i2c_write_buf(struct bbc_i2c_client *, char *buf, int len, int off);
+extern int bbc_i2c_read_buf(struct bbc_i2c_client *, char *buf, int len, int off);
+
+#endif /* _BBC_I2C_H */
diff --git a/drivers/sbus/char/bpp.c b/drivers/sbus/char/bpp.c
new file mode 100644
index 0000000..8f0f469
--- /dev/null
+++ b/drivers/sbus/char/bpp.c
@@ -0,0 +1,1079 @@
+/*
+ * drivers/sbus/char/bpp.c
+ *
+ * Copyright (c) 1995 Picture Elements
+ *      Stephen Williams (steve@icarus.com)
+ *      Gus Baldauf (gbaldauf@ix.netcom.com)
+ *
+ * Linux/SPARC port by Peter Zaitcev.
+ * Integration into SPARC tree by Tom Dyas.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/ioport.h>
+#include <linux/major.h>
+#include <linux/devfs_fs_kernel.h>
+
+#include <asm/uaccess.h>
+#include <asm/io.h>
+
+#if defined(__i386__)
+# include <asm/system.h>
+#endif
+
+#if defined(__sparc__)
+# include <linux/init.h>
+# include <linux/delay.h>         /* udelay() */
+
+# include <asm/oplib.h>           /* OpenProm Library */
+# include <asm/sbus.h>
+#endif
+
+#include <asm/bpp.h>
+
+#define BPP_PROBE_CODE 0x55
+#define BPP_DELAY 100
+
+static const unsigned  BPP_MAJOR = LP_MAJOR;
+static const char* dev_name = "bpp";
+
+/* When switching from compatibility to a mode where I can read, try
+   the following mode first. */
+
+/* const unsigned char DEFAULT_ECP = 0x10; */
+static const unsigned char DEFAULT_ECP = 0x30;
+static const unsigned char DEFAULT_NIBBLE = 0x00;
+
+/*
+ * These are 1284 time constraints, in units of jiffies.
+ */
+
+static const unsigned long TIME_PSetup = 1;
+static const unsigned long TIME_PResponse = 6;
+static const unsigned long TIME_IDLE_LIMIT = 2000;
+
+/*
+ * One instance per supported subdevice...
+ */
+# define BPP_NO 3
+
+enum IEEE_Mode { COMPATIBILITY, NIBBLE, ECP, ECP_RLE, EPP };
+
+struct inst {
+      unsigned present  : 1; /* True if the hardware exists */
+      unsigned enhanced : 1; /* True if the hardware in "enhanced" */
+      unsigned opened   : 1; /* True if the device is opened already */
+      unsigned run_flag : 1; /* True if waiting for a repeate byte */
+
+      unsigned char direction; /* 0 --> out, 0x20 --> IN */
+      unsigned char pp_state; /* State of host controlled pins. */
+      enum IEEE_Mode mode;
+
+      unsigned char run_length;
+      unsigned char repeat_byte;
+
+      /* These members manage timeouts for programmed delays */
+      wait_queue_head_t wait_queue;
+      struct timer_list timer_list;
+};
+
+static struct inst instances[BPP_NO];
+
+#if defined(__i386__)
+
+static const unsigned short base_addrs[BPP_NO] = { 0x278, 0x378, 0x3bc };
+
+/*
+ * These are for data access.
+ * Control lines accesses are hidden in set_bits() and get_bits().
+ * The exception is the probe procedure, which is system-dependent.
+ */
+#define bpp_outb_p(data, base)  outb_p((data), (base))
+#define bpp_inb(base)  inb(base)
+#define bpp_inb_p(base)  inb_p(base)
+
+/*
+ * This method takes the pin values mask and sets the hardware pins to
+ * the requested value: 1 == high voltage, 0 == low voltage. This
+ * burries the annoying PC bit inversion and preserves the direction
+ * flag.
+ */
+static void set_pins(unsigned short pins, unsigned minor)
+{
+      unsigned char bits = instances[minor].direction;  /* == 0x20 */
+
+      if (! (pins & BPP_PP_nStrobe))   bits |= 1;
+      if (! (pins & BPP_PP_nAutoFd))   bits |= 2;
+      if (   pins & BPP_PP_nInit)      bits |= 4;
+      if (! (pins & BPP_PP_nSelectIn)) bits |= 8;
+
+      instances[minor].pp_state = bits;
+
+      outb_p(bits, base_addrs[minor]+2);
+}
+
+static unsigned short get_pins(unsigned minor)
+{
+      unsigned short bits = 0;
+
+      unsigned value = instances[minor].pp_state;
+      if (! (value & 0x01)) bits |= BPP_PP_nStrobe;
+      if (! (value & 0x02)) bits |= BPP_PP_nAutoFd;
+      if (value & 0x04)     bits |= BPP_PP_nInit;
+      if (! (value & 0x08)) bits |= BPP_PP_nSelectIn;
+
+      value = inb_p(base_addrs[minor]+1);
+      if (value & 0x08)     bits |= BPP_GP_nFault;
+      if (value & 0x10)     bits |= BPP_GP_Select;
+      if (value & 0x20)     bits |= BPP_GP_PError;
+      if (value & 0x40)     bits |= BPP_GP_nAck;
+      if (! (value & 0x80)) bits |= BPP_GP_Busy;
+
+      return bits;
+}
+
+#endif /* __i386__ */
+
+#if defined(__sparc__)
+
+/*
+ * Register block
+ */
+      /* DMA registers */
+#define BPP_CSR      0x00
+#define BPP_ADDR     0x04
+#define BPP_BCNT     0x08
+#define BPP_TST_CSR  0x0C
+      /* Parallel Port registers */
+#define BPP_HCR      0x10
+#define BPP_OCR      0x12
+#define BPP_DR       0x14
+#define BPP_TCR      0x15
+#define BPP_OR       0x16
+#define BPP_IR       0x17
+#define BPP_ICR      0x18
+#define BPP_SIZE     0x1A
+
+/* BPP_CSR.  Bits of type RW1 are cleared with writting '1'. */
+#define P_DEV_ID_MASK   0xf0000000      /* R   */
+#define P_DEV_ID_ZEBRA  0x40000000
+#define P_DEV_ID_L64854 0xa0000000      /*      == NCR 89C100+89C105. Pity. */
+#define P_NA_LOADED     0x08000000      /* R    NA wirtten but was not used */
+#define P_A_LOADED      0x04000000      /* R    */
+#define P_DMA_ON        0x02000000      /* R    DMA is not disabled */
+#define P_EN_NEXT       0x01000000      /* RW   */
+#define P_TCI_DIS       0x00800000      /* RW   TCI forbidden from interrupts */
+#define P_DIAG          0x00100000      /* RW   Disables draining and resetting
+                                                of P-FIFO on loading of P_ADDR*/
+#define P_BURST_SIZE    0x000c0000      /* RW   SBus burst size */
+#define P_BURST_8       0x00000000
+#define P_BURST_4       0x00040000
+#define P_BURST_1       0x00080000      /*      "No burst" write */
+#define P_TC            0x00004000      /* RW1  Term Count, can be cleared when
+                                           P_EN_NEXT=1 */
+#define P_EN_CNT        0x00002000      /* RW   */
+#define P_EN_DMA        0x00000200      /* RW   */
+#define P_WRITE         0x00000100      /* R    DMA dir, 1=to ram, 0=to port */
+#define P_RESET         0x00000080      /* RW   */
+#define P_SLAVE_ERR     0x00000040      /* RW1  Access size error */
+#define P_INVALIDATE    0x00000020      /* W    Drop P-FIFO */
+#define P_INT_EN        0x00000010      /* RW   OK to P_INT_PEND||P_ERR_PEND */
+#define P_DRAINING      0x0000000c      /* R    P-FIFO is draining to memory */
+#define P_ERR_PEND      0x00000002      /* R    */
+#define P_INT_PEND      0x00000001      /* R    */
+
+/* BPP_HCR. Time is in increments of SBus clock. */
+#define P_HCR_TEST      0x8000      /* Allows buried counters to be read */
+#define P_HCR_DSW       0x7f00      /* Data strobe width (in ticks) */
+#define P_HCR_DDS       0x007f      /* Data setup before strobe (in ticks) */
+
+/* BPP_OCR. */
+#define P_OCR_MEM_CLR   0x8000
+#define P_OCR_DATA_SRC  0x4000      /* )                  */
+#define P_OCR_DS_DSEL   0x2000      /* )  Bidirectional      */
+#define P_OCR_BUSY_DSEL 0x1000      /* )    selects            */
+#define P_OCR_ACK_DSEL  0x0800      /* )                  */
+#define P_OCR_EN_DIAG   0x0400
+#define P_OCR_BUSY_OP   0x0200      /* Busy operation */
+#define P_OCR_ACK_OP    0x0100      /* Ack operation */
+#define P_OCR_SRST      0x0080      /* Reset state machines. Not selfcleaning. */
+#define P_OCR_IDLE      0x0008      /* PP data transfer state machine is idle */
+#define P_OCR_V_ILCK    0x0002      /* Versatec faded. Zebra only. */
+#define P_OCR_EN_VER    0x0001      /* Enable Versatec (0 - enable). Zebra only. */
+
+/* BPP_TCR */
+#define P_TCR_DIR       0x08
+#define P_TCR_BUSY      0x04
+#define P_TCR_ACK       0x02
+#define P_TCR_DS        0x01        /* Strobe */
+
+/* BPP_OR */
+#define P_OR_V3         0x20        /* )                 */
+#define P_OR_V2         0x10        /* ) on Zebra only   */
+#define P_OR_V1         0x08        /* )                 */
+#define P_OR_INIT       0x04
+#define P_OR_AFXN       0x02        /* Auto Feed */
+#define P_OR_SLCT_IN    0x01
+
+/* BPP_IR */
+#define P_IR_PE         0x04
+#define P_IR_SLCT       0x02
+#define P_IR_ERR        0x01
+
+/* BPP_ICR */
+#define P_DS_IRQ        0x8000      /* RW1  */
+#define P_ACK_IRQ       0x4000      /* RW1  */
+#define P_BUSY_IRQ      0x2000      /* RW1  */
+#define P_PE_IRQ        0x1000      /* RW1  */
+#define P_SLCT_IRQ      0x0800      /* RW1  */
+#define P_ERR_IRQ       0x0400      /* RW1  */
+#define P_DS_IRQ_EN     0x0200      /* RW   Always on rising edge */
+#define P_ACK_IRQ_EN    0x0100      /* RW   Always on rising edge */
+#define P_BUSY_IRP      0x0080      /* RW   1= rising edge */
+#define P_BUSY_IRQ_EN   0x0040      /* RW   */
+#define P_PE_IRP        0x0020      /* RW   1= rising edge */
+#define P_PE_IRQ_EN     0x0010      /* RW   */
+#define P_SLCT_IRP      0x0008      /* RW   1= rising edge */
+#define P_SLCT_IRQ_EN   0x0004      /* RW   */
+#define P_ERR_IRP       0x0002      /* RW1  1= rising edge */
+#define P_ERR_IRQ_EN    0x0001      /* RW   */
+
+static void __iomem *base_addrs[BPP_NO];
+
+#define bpp_outb_p(data, base)	sbus_writeb(data, (base) + BPP_DR)
+#define bpp_inb_p(base)		sbus_readb((base) + BPP_DR)
+#define bpp_inb(base)		sbus_readb((base) + BPP_DR)
+
+static void set_pins(unsigned short pins, unsigned minor)
+{
+      void __iomem *base = base_addrs[minor];
+      unsigned char bits_tcr = 0, bits_or = 0;
+
+      if (instances[minor].direction & 0x20) bits_tcr |= P_TCR_DIR;
+      if (   pins & BPP_PP_nStrobe)          bits_tcr |= P_TCR_DS;
+
+      if (   pins & BPP_PP_nAutoFd)          bits_or |= P_OR_AFXN;
+      if (! (pins & BPP_PP_nInit))           bits_or |= P_OR_INIT;
+      if (! (pins & BPP_PP_nSelectIn))       bits_or |= P_OR_SLCT_IN;
+
+      sbus_writeb(bits_or, base + BPP_OR);
+      sbus_writeb(bits_tcr, base + BPP_TCR);
+}
+
+/*
+ * i386 people read output pins from a software image.
+ * We may get them back from hardware.
+ * Again, inversion of pins must he buried here.
+ */
+static unsigned short get_pins(unsigned minor)
+{
+      void __iomem *base = base_addrs[minor];
+      unsigned short bits = 0;
+      unsigned value_tcr = sbus_readb(base + BPP_TCR);
+      unsigned value_ir = sbus_readb(base + BPP_IR);
+      unsigned value_or = sbus_readb(base + BPP_OR);
+
+      if (value_tcr & P_TCR_DS)         bits |= BPP_PP_nStrobe;
+      if (value_or & P_OR_AFXN)         bits |= BPP_PP_nAutoFd;
+      if (! (value_or & P_OR_INIT))     bits |= BPP_PP_nInit;
+      if (! (value_or & P_OR_SLCT_IN))  bits |= BPP_PP_nSelectIn;
+
+      if (value_ir & P_IR_ERR)          bits |= BPP_GP_nFault;
+      if (! (value_ir & P_IR_SLCT))     bits |= BPP_GP_Select;
+      if (! (value_ir & P_IR_PE))       bits |= BPP_GP_PError;
+      if (! (value_tcr & P_TCR_ACK))    bits |= BPP_GP_nAck;
+      if (value_tcr & P_TCR_BUSY)       bits |= BPP_GP_Busy;
+
+      return bits;
+}
+
+#endif /* __sparc__ */
+
+static void bpp_wake_up(unsigned long val)
+{ wake_up(&instances[val].wait_queue); }
+
+static void snooze(unsigned long snooze_time, unsigned minor)
+{
+      init_timer(&instances[minor].timer_list);
+      instances[minor].timer_list.expires = jiffies + snooze_time + 1;
+      instances[minor].timer_list.data    = minor;
+      add_timer(&instances[minor].timer_list);
+      sleep_on (&instances[minor].wait_queue);
+}
+
+static int wait_for(unsigned short set, unsigned short clr,
+               unsigned long delay, unsigned minor)
+{
+      unsigned short pins = get_pins(minor);
+
+      unsigned long extime = 0;
+
+      /*
+       * Try a real fast scan for the first jiffy, in case the device
+       * responds real good. The first while loop guesses an expire
+       * time accounting for possible wraparound of jiffies.
+       */
+      while (time_after_eq(jiffies, extime)) extime = jiffies + 1;
+      while ( (time_before(jiffies, extime))
+              && (((pins & set) != set) || ((pins & clr) != 0)) ) {
+            pins = get_pins(minor);
+      }
+
+      delay -= 1;
+
+      /*
+       * If my delay expired or the pins are still not where I want
+       * them, then resort to using the timer and greatly reduce my
+       * sample rate. If the peripheral is going to be slow, this will
+       * give the CPU up to some more worthy process.
+       */
+      while ( delay && (((pins & set) != set) || ((pins & clr) != 0)) ) {
+
+            snooze(1, minor);
+            pins = get_pins(minor);
+            delay -= 1;
+      }
+
+      if (delay == 0) return -1;
+      else return pins;
+}
+
+/*
+ * Return ZERO(0) If the negotiation succeeds, an errno otherwise. An
+ * errno means something broke, and I do not yet know how to fix it.
+ */
+static int negotiate(unsigned char mode, unsigned minor)
+{
+      int rc;
+      unsigned short pins = get_pins(minor);
+      if (pins & BPP_PP_nSelectIn) return -EIO;
+
+
+        /* Event 0: Write the mode to the data lines */
+      bpp_outb_p(mode, base_addrs[minor]);
+
+      snooze(TIME_PSetup, minor);
+
+        /* Event 1: Strobe the mode code into the peripheral */
+      set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+        /* Wait for Event 2: Peripheral responds as a 1284 device. */
+      rc = wait_for(BPP_GP_PError|BPP_GP_Select|BPP_GP_nFault,
+                BPP_GP_nAck,
+                TIME_PResponse,
+                minor);
+
+      if (rc == -1) return -ETIMEDOUT;
+
+        /* Event 3: latch extensibility request */
+      set_pins(BPP_PP_nSelectIn|BPP_PP_nInit, minor);
+
+        /* ... quick nap while peripheral ponders the byte i'm sending...*/
+      snooze(1, minor);
+
+        /* Event 4: restore strobe, to ACK peripheral's response. */
+      set_pins(BPP_PP_nSelectIn|BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+        /* Wait for Event 6: Peripheral latches response bits */
+      rc = wait_for(BPP_GP_nAck, 0, TIME_PSetup+TIME_PResponse, minor);
+      if (rc == -1) return -EIO;
+
+        /* A 1284 device cannot refuse nibble mode */
+      if (mode == DEFAULT_NIBBLE) return 0;
+
+      if (pins & BPP_GP_Select) return 0;
+
+      return -EPROTONOSUPPORT;
+}
+
+static int terminate(unsigned minor)
+{
+      int rc;
+
+        /* Event 22: Request termination of 1284 mode */
+      set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+        /* Wait for Events 23 and 24: ACK termination request. */
+      rc = wait_for(BPP_GP_Busy|BPP_GP_nFault,
+                BPP_GP_nAck,
+                TIME_PSetup+TIME_PResponse,
+                minor);
+
+      instances[minor].direction = 0;
+      instances[minor].mode = COMPATIBILITY;
+
+      if (rc == -1) {
+          return -EIO;
+      }
+
+        /* Event 25: Handshake by lowering nAutoFd */
+      set_pins(BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+        /* Event 26: Peripheral wiggles lines... */
+
+        /* Event 27: Peripheral sets nAck HIGH to ack handshake */
+      rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+      if (rc == -1) {
+          set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+          return -EIO;
+      }
+
+        /* Event 28: Finish phase by raising nAutoFd */
+      set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, minor);
+
+      return 0;
+}
+
+static DEFINE_SPINLOCK(bpp_open_lock);
+
+/*
+ * Allow only one process to open the device at a time.
+ */
+static int bpp_open(struct inode *inode, struct file *f)
+{
+      unsigned minor = iminor(inode);
+      int ret;
+
+      spin_lock(&bpp_open_lock);
+      ret = 0;
+      if (minor >= BPP_NO) {
+	      ret = -ENODEV;
+      } else {
+	      if (! instances[minor].present) {
+		      ret = -ENODEV;
+	      } else {
+		      if (instances[minor].opened) 
+			      ret = -EBUSY;
+		      else
+			      instances[minor].opened = 1;
+	      }
+      }
+      spin_unlock(&bpp_open_lock);
+
+      return ret;
+}
+
+/*
+ * When the process closes the device, this method is called to clean
+ * up and reset the hardware. Always leave the device in compatibility
+ * mode as this is a reasonable place to clean up from messes made by
+ * ioctls, or other mayhem.
+ */
+static int bpp_release(struct inode *inode, struct file *f)
+{
+      unsigned minor = iminor(inode);
+
+      spin_lock(&bpp_open_lock);
+      instances[minor].opened = 0;
+
+      if (instances[minor].mode != COMPATIBILITY)
+	      terminate(minor);
+
+      spin_unlock(&bpp_open_lock);
+
+      return 0;
+}
+
+static long read_nibble(unsigned minor, char __user *c, unsigned long cnt)
+{
+      unsigned long remaining = cnt;
+      long rc;
+
+      while (remaining > 0) {
+          unsigned char byte = 0;
+          int pins;
+
+          /* Event 7: request nibble */
+          set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe, minor);
+
+          /* Wait for event 9: Peripher strobes first nibble */
+          pins = wait_for(0, BPP_GP_nAck, TIME_IDLE_LIMIT, minor);
+          if (pins == -1) return -ETIMEDOUT;
+
+          /* Event 10: I handshake nibble */
+          set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nAutoFd, minor);
+          if (pins & BPP_GP_nFault) byte |= 0x01;
+          if (pins & BPP_GP_Select) byte |= 0x02;
+          if (pins & BPP_GP_PError) byte |= 0x04;
+          if (pins & BPP_GP_Busy)   byte |= 0x08;
+
+          /* Wait for event 11: Peripheral handshakes nibble */
+          rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+
+          /* Event 7: request nibble */
+          set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe, minor);
+
+          /* Wait for event 9: Peripher strobes first nibble */
+          pins = wait_for(0, BPP_GP_nAck, TIME_PResponse, minor);
+          if (rc == -1) return -ETIMEDOUT;
+
+          /* Event 10: I handshake nibble */
+          set_pins(BPP_PP_nSelectIn|BPP_PP_nStrobe|BPP_PP_nAutoFd, minor);
+          if (pins & BPP_GP_nFault) byte |= 0x10;
+          if (pins & BPP_GP_Select) byte |= 0x20;
+          if (pins & BPP_GP_PError) byte |= 0x40;
+          if (pins & BPP_GP_Busy)   byte |= 0x80;
+
+          if (put_user(byte, c))
+		  return -EFAULT;
+          c += 1;
+          remaining -= 1;
+
+          /* Wait for event 11: Peripheral handshakes nibble */
+          rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+          if (rc == -1) return -EIO;
+      }
+
+      return cnt - remaining;
+}
+
+static long read_ecp(unsigned minor, char __user *c, unsigned long cnt)
+{
+      unsigned long remaining;
+      long rc;
+
+        /* Turn ECP mode from forward to reverse if needed. */
+      if (! instances[minor].direction) {
+          unsigned short pins = get_pins(minor);
+
+            /* Event 38: Turn the bus around */
+          instances[minor].direction = 0x20;
+          pins &= ~BPP_PP_nAutoFd;
+          set_pins(pins, minor);
+
+            /* Event 39: Set pins for reverse mode. */
+          snooze(TIME_PSetup, minor);
+          set_pins(BPP_PP_nStrobe|BPP_PP_nSelectIn, minor);
+
+            /* Wait for event 40: Peripheral ready to be strobed */
+          rc = wait_for(0, BPP_GP_PError, TIME_PResponse, minor);
+          if (rc == -1) return -ETIMEDOUT;
+      }
+
+      remaining = cnt;
+
+      while (remaining > 0) {
+
+            /* If there is a run length for a repeated byte, repeat */
+            /* that byte a few times. */
+          if (instances[minor].run_length && !instances[minor].run_flag) {
+
+              char buffer[128];
+              unsigned idx;
+              unsigned repeat = remaining < instances[minor].run_length
+                                     ? remaining
+                               : instances[minor].run_length;
+
+              for (idx = 0 ;  idx < repeat ;  idx += 1)
+                buffer[idx] = instances[minor].repeat_byte;
+
+              if (copy_to_user(c, buffer, repeat))
+		      return -EFAULT;
+              remaining -= repeat;
+              c += repeat;
+              instances[minor].run_length -= repeat;
+          }
+
+          if (remaining == 0) break;
+
+
+            /* Wait for Event 43: Data active on the bus. */
+          rc = wait_for(0, BPP_GP_nAck, TIME_IDLE_LIMIT, minor);
+          if (rc == -1) break;
+
+          if (rc & BPP_GP_Busy) {
+                /* OK, this is data. read it in. */
+              unsigned char byte = bpp_inb(base_addrs[minor]);
+              if (put_user(byte, c))
+		      return -EFAULT;
+              c += 1;
+              remaining -= 1;
+
+              if (instances[minor].run_flag) {
+                  instances[minor].repeat_byte = byte;
+                  instances[minor].run_flag = 0;
+              }
+
+          } else {
+              unsigned char byte = bpp_inb(base_addrs[minor]);
+              if (byte & 0x80) {
+                  printk("bpp%d: "
+                         "Ignoring ECP channel %u from device.\n",
+                         minor, byte & 0x7f);
+              } else {
+                  instances[minor].run_length = byte;
+                  instances[minor].run_flag = 1;
+              }
+          }
+
+            /* Event 44: I got it. */
+          set_pins(BPP_PP_nStrobe|BPP_PP_nAutoFd|BPP_PP_nSelectIn, minor);
+
+            /* Wait for event 45: peripheral handshake */
+          rc = wait_for(BPP_GP_nAck, 0, TIME_PResponse, minor);
+          if (rc == -1) return -ETIMEDOUT;
+
+             /* Event 46: Finish handshake */
+          set_pins(BPP_PP_nStrobe|BPP_PP_nSelectIn, minor);
+
+      }
+
+
+      return cnt - remaining;
+}
+
+static ssize_t bpp_read(struct file *f, char __user *c, size_t cnt, loff_t * ppos)
+{
+      long rc;
+      unsigned minor = iminor(f->f_dentry->d_inode);
+      if (minor >= BPP_NO) return -ENODEV;
+      if (!instances[minor].present) return -ENODEV;
+
+      switch (instances[minor].mode) {
+
+        default:
+          if (instances[minor].mode != COMPATIBILITY)
+            terminate(minor);
+
+          if (instances[minor].enhanced) {
+              /* For now, do all reads with ECP-RLE mode */
+              unsigned short pins;
+
+              rc = negotiate(DEFAULT_ECP, minor);
+              if (rc < 0) break;
+
+              instances[minor].mode = ECP_RLE;
+
+              /* Event 30: set nAutoFd low to setup for ECP mode */
+              pins = get_pins(minor);
+              pins &= ~BPP_PP_nAutoFd;
+              set_pins(pins, minor);
+
+              /* Wait for Event 31: peripheral ready */
+              rc = wait_for(BPP_GP_PError, 0, TIME_PResponse, minor);
+              if (rc == -1) return -ETIMEDOUT;
+
+              rc = read_ecp(minor, c, cnt);
+
+          } else {
+              rc = negotiate(DEFAULT_NIBBLE, minor);
+              if (rc < 0) break;
+
+              instances[minor].mode = NIBBLE;
+
+              rc = read_nibble(minor, c, cnt);
+          }
+          break;
+
+        case NIBBLE:
+          rc = read_nibble(minor, c, cnt);
+          break;
+
+        case ECP:
+        case ECP_RLE:
+          rc = read_ecp(minor, c, cnt);
+          break;
+
+      }
+
+
+      return rc;
+}
+
+/*
+ * Compatibility mode handshaking is a matter of writing data,
+ * strobing it, and waiting for the printer to stop being busy.
+ */
+static long write_compat(unsigned minor, const char __user *c, unsigned long cnt)
+{
+      long rc;
+      unsigned short pins = get_pins(minor);
+
+      unsigned long remaining = cnt;
+
+
+      while (remaining > 0) {
+            unsigned char byte;
+
+            if (get_user(byte, c))
+		    return -EFAULT;
+            c += 1;
+
+            rc = wait_for(BPP_GP_nAck, BPP_GP_Busy, TIME_IDLE_LIMIT, minor);
+            if (rc == -1) return -ETIMEDOUT;
+
+            bpp_outb_p(byte, base_addrs[minor]);
+            remaining -= 1;
+          /* snooze(1, minor); */
+
+          pins &= ~BPP_PP_nStrobe;
+          set_pins(pins, minor);
+
+          rc = wait_for(BPP_GP_Busy, 0, TIME_PResponse, minor);
+
+          pins |= BPP_PP_nStrobe;
+          set_pins(pins, minor);
+      }
+
+      return cnt - remaining;
+}
+
+/*
+ * Write data using ECP mode. Watch out that the port may be set up
+ * for reading. If so, turn the port around.
+ */
+static long write_ecp(unsigned minor, const char __user *c, unsigned long cnt)
+{
+      unsigned short pins = get_pins(minor);
+      unsigned long remaining = cnt;
+
+      if (instances[minor].direction) {
+          int rc;
+
+            /* Event 47 Request bus be turned around */
+          pins |= BPP_PP_nInit;
+          set_pins(pins, minor);
+
+            /* Wait for Event 49: Peripheral relinquished bus */
+          rc = wait_for(BPP_GP_PError, 0, TIME_PResponse, minor);
+
+          pins |= BPP_PP_nAutoFd;
+          instances[minor].direction = 0;
+          set_pins(pins, minor);
+      }
+
+      while (remaining > 0) {
+          unsigned char byte;
+          int rc;
+
+          if (get_user(byte, c))
+		  return -EFAULT;
+
+          rc = wait_for(0, BPP_GP_Busy, TIME_PResponse, minor);
+          if (rc == -1) return -ETIMEDOUT;
+
+          c += 1;
+
+          bpp_outb_p(byte, base_addrs[minor]);
+
+          pins &= ~BPP_PP_nStrobe;
+          set_pins(pins, minor);
+
+          pins |= BPP_PP_nStrobe;
+          rc = wait_for(BPP_GP_Busy, 0, TIME_PResponse, minor);
+          if (rc == -1) return -EIO;
+
+          set_pins(pins, minor);
+      }
+
+      return cnt - remaining;
+}
+
+/*
+ * Write to the peripheral. Be sensitive of the current mode. If I'm
+ * in a mode that can be turned around (ECP) then just do
+ * that. Otherwise, terminate and do my writing in compat mode. This
+ * is the safest course as any device can handle it.
+ */
+static ssize_t bpp_write(struct file *f, const char __user *c, size_t cnt, loff_t * ppos)
+{
+      long errno = 0;
+      unsigned minor = iminor(f->f_dentry->d_inode);
+      if (minor >= BPP_NO) return -ENODEV;
+      if (!instances[minor].present) return -ENODEV;
+
+      switch (instances[minor].mode) {
+
+        case ECP:
+        case ECP_RLE:
+          errno = write_ecp(minor, c, cnt);
+          break;
+        case COMPATIBILITY:
+          errno = write_compat(minor, c, cnt);
+          break;
+        default:
+          terminate(minor);
+          errno = write_compat(minor, c, cnt);
+      }
+
+      return errno;
+}
+
+static int bpp_ioctl(struct inode *inode, struct file *f, unsigned int cmd,
+		 unsigned long arg)
+{
+      int errno = 0;
+
+      unsigned minor = iminor(inode);
+      if (minor >= BPP_NO) return -ENODEV;
+      if (!instances[minor].present) return -ENODEV;
+
+
+      switch (cmd) {
+
+        case BPP_PUT_PINS:
+          set_pins(arg, minor);
+          break;
+
+        case BPP_GET_PINS:
+          errno = get_pins(minor);
+          break;
+
+        case BPP_PUT_DATA:
+          bpp_outb_p(arg, base_addrs[minor]);
+          break;
+
+        case BPP_GET_DATA:
+          errno = bpp_inb_p(base_addrs[minor]);
+          break;
+
+        case BPP_SET_INPUT:
+          if (arg)
+            if (instances[minor].enhanced) {
+                unsigned short bits = get_pins(minor);
+                instances[minor].direction = 0x20;
+                set_pins(bits, minor);
+            } else {
+                errno = -ENOTTY;
+            }
+          else {
+              unsigned short bits = get_pins(minor);
+              instances[minor].direction = 0x00;
+              set_pins(bits, minor);
+          }
+          break;
+
+        default:
+            errno = -EINVAL;
+      }
+
+      return errno;
+}
+
+static struct file_operations bpp_fops = {
+	.owner =	THIS_MODULE,
+	.read =		bpp_read,
+	.write =	bpp_write,
+	.ioctl =	bpp_ioctl,
+	.open =		bpp_open,
+	.release =	bpp_release,
+};
+
+#if defined(__i386__)
+
+#define collectLptPorts()  {}
+
+static void probeLptPort(unsigned idx)
+{
+      unsigned int testvalue;
+      const unsigned short lpAddr = base_addrs[idx];
+
+      instances[idx].present = 0;
+      instances[idx].enhanced = 0;
+      instances[idx].direction = 0;
+      instances[idx].mode = COMPATIBILITY;
+      instances[idx].wait_queue = 0;
+      instances[idx].run_length = 0;
+      instances[idx].run_flag = 0;
+      init_timer(&instances[idx].timer_list);
+      instances[idx].timer_list.function = bpp_wake_up;
+      if (!request_region(lpAddr,3, dev_name)) return;
+
+      /*
+       * First, make sure the instance exists. Do this by writing to
+       * the data latch and reading the value back. If the port *is*
+       * present, test to see if it supports extended-mode
+       * operation. This will be required for IEEE1284 reverse
+       * transfers.
+       */
+
+      outb_p(BPP_PROBE_CODE, lpAddr);
+      for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+            ;
+      testvalue = inb_p(lpAddr);
+      if (testvalue == BPP_PROBE_CODE) {
+            unsigned save;
+            instances[idx].present = 1;
+
+            save = inb_p(lpAddr+2);
+            for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+                  ;
+            outb_p(save|0x20, lpAddr+2);
+            for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+                  ;
+            outb_p(~BPP_PROBE_CODE, lpAddr);
+            for (testvalue=0; testvalue<BPP_DELAY; testvalue++)
+                  ;
+            testvalue = inb_p(lpAddr);
+            if ((testvalue&0xff) == (0xff&~BPP_PROBE_CODE))
+                  instances[idx].enhanced = 0;
+            else
+                  instances[idx].enhanced = 1;
+            outb_p(save, lpAddr+2);
+      }
+      else {
+            release_region(lpAddr,3);
+      }
+      /*
+       * Leave the port in compat idle mode.
+       */
+      set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, idx);
+
+      printk("bpp%d: Port at 0x%03x: Enhanced mode %s\n", idx, base_addrs[idx],
+            instances[idx].enhanced? "SUPPORTED" : "UNAVAILABLE");
+}
+
+static inline void freeLptPort(int idx)
+{
+      release_region(base_addrs[idx], 3);
+}
+
+#endif
+
+#if defined(__sparc__)
+
+static void __iomem *map_bpp(struct sbus_dev *dev, int idx)
+{
+      return sbus_ioremap(&dev->resource[0], 0, BPP_SIZE, "bpp");
+}
+
+static int collectLptPorts(void)
+{
+	struct sbus_bus *bus;
+	struct sbus_dev *dev;
+	int count;
+
+	count = 0;
+	for_all_sbusdev(dev, bus) {
+		if (strcmp(dev->prom_name, "SUNW,bpp") == 0) {
+			if (count >= BPP_NO) {
+				printk(KERN_NOTICE
+				       "bpp: More than %d bpp ports,"
+				       " rest is ignored\n", BPP_NO);
+				return count;
+			}
+			base_addrs[count] = map_bpp(dev, count);
+			count++;
+		}
+	}
+	return count;
+}
+
+static void probeLptPort(unsigned idx)
+{
+      void __iomem *rp = base_addrs[idx];
+      __u32 csr;
+      char *brand;
+
+      instances[idx].present = 0;
+      instances[idx].enhanced = 0;
+      instances[idx].direction = 0;
+      instances[idx].mode = COMPATIBILITY;
+      init_waitqueue_head(&instances[idx].wait_queue);
+      instances[idx].run_length = 0;
+      instances[idx].run_flag = 0;
+      init_timer(&instances[idx].timer_list);
+      instances[idx].timer_list.function = bpp_wake_up;
+
+      if (!rp) return;
+
+      instances[idx].present = 1;
+      instances[idx].enhanced = 1;   /* Sure */
+
+      csr = sbus_readl(rp + BPP_CSR);
+      if ((csr & P_DRAINING) != 0 && (csr & P_ERR_PEND) == 0) {
+            udelay(20);
+            csr = sbus_readl(rp + BPP_CSR);
+            if ((csr & P_DRAINING) != 0 && (csr & P_ERR_PEND) == 0) {
+                  printk("bpp%d: DRAINING still active (0x%08x)\n", idx, csr);
+            }
+      }
+      printk("bpp%d: reset with 0x%08x ..", idx, csr);
+      sbus_writel((csr | P_RESET) & ~P_INT_EN, rp + BPP_CSR);
+      udelay(500);
+      sbus_writel(sbus_readl(rp + BPP_CSR) & ~P_RESET, rp + BPP_CSR);
+      csr = sbus_readl(rp + BPP_CSR);
+      printk(" done with csr=0x%08x ocr=0x%04x\n",
+         csr, sbus_readw(rp + BPP_OCR));
+
+      switch (csr & P_DEV_ID_MASK) {
+      case P_DEV_ID_ZEBRA:
+            brand = "Zebra";
+            break;
+      case P_DEV_ID_L64854:
+            brand = "DMA2";
+            break;
+      default:
+            brand = "Unknown";
+      }
+      printk("bpp%d: %s at %p\n", idx, brand, rp);
+
+      /*
+       * Leave the port in compat idle mode.
+       */
+      set_pins(BPP_PP_nAutoFd|BPP_PP_nStrobe|BPP_PP_nInit, idx);
+
+      return;
+}
+
+static inline void freeLptPort(int idx)
+{
+      sbus_iounmap(base_addrs[idx], BPP_SIZE);
+}
+
+#endif
+
+static int __init bpp_init(void)
+{
+	int rc;
+	unsigned idx;
+
+	rc = collectLptPorts();
+	if (rc == 0)
+		return -ENODEV;
+
+	rc = register_chrdev(BPP_MAJOR, dev_name, &bpp_fops);
+	if (rc < 0)
+		return rc;
+
+	for (idx = 0; idx < BPP_NO; idx++) {
+		instances[idx].opened = 0;
+		probeLptPort(idx);
+	}
+	devfs_mk_dir("bpp");
+	for (idx = 0; idx < BPP_NO; idx++) {
+		devfs_mk_cdev(MKDEV(BPP_MAJOR, idx),
+				S_IFCHR | S_IRUSR | S_IWUSR, "bpp/%d", idx);
+	}
+
+	return 0;
+}
+
+static void __exit bpp_cleanup(void)
+{
+	unsigned idx;
+
+	for (idx = 0; idx < BPP_NO; idx++)
+		devfs_remove("bpp/%d", idx);
+	devfs_remove("bpp");
+	unregister_chrdev(BPP_MAJOR, dev_name);
+
+	for (idx = 0;  idx < BPP_NO; idx++) {
+		if (instances[idx].present)
+			freeLptPort(idx);
+	}
+}
+
+module_init(bpp_init);
+module_exit(bpp_cleanup);
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/sbus/char/cd180.h b/drivers/sbus/char/cd180.h
new file mode 100644
index 0000000..445b86c
--- /dev/null
+++ b/drivers/sbus/char/cd180.h
@@ -0,0 +1,240 @@
+
+/* 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_GFRCR	0x6b	/* Global Firmware Revision Code Register  */
+#define CD180_SRCR	0x66	/* Service Request Configuration Register  */
+#define CD180_PPRH	0x70	/* Prescaler Period Register High	   */
+#define CD180_PPRL	0x71	/* Prescaler Period Register Low	   */
+#define CD180_MSMR	0x61	/* Modem Service Match Register		   */
+#define CD180_TSMR	0x62	/* Transmit Service Match Register	   */
+#define CD180_RSMR	0x63	/* Receive Service Match Register	   */
+#define CD180_GSVR	0x40	/* Global Service Vector Register	   */
+#define CD180_SRSR	0x65	/* Service Request Status Register	   */
+#define CD180_GSCR	0x41	/* Global Service Channel Register	   */
+#define CD180_CAR	0x64	/* Channel Access Register		   */
+
+/* Indexed registers */
+#define CD180_RDCR	0x07	/* Receive Data Count Register		   */
+#define CD180_RDR	0x78	/* Receiver Data Register		   */
+#define CD180_RCSR	0x7a	/* Receiver Character Status Register	   */
+#define CD180_TDR	0x7b	/* Transmit Data Register		   */
+#define CD180_EOSRR	0x7f	/* End of Service Request Register	   */
+
+/* Channel Registers */
+#define CD180_SRER      0x02    /* Service Request Enable Register         */
+#define CD180_CCR       0x01    /* Channel Command 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_RTPR      0x18    /* Receive Timeout Period Register         */
+#define CD180_RBPRH     0x31    /* Receive Bit Rate Period Register High  */
+#define CD180_RBPRL     0x32    /* Receive Bit Rate Period Register Low   */
+#define CD180_TBPRH     0x39    /* Transmit Bit Rate Period Register High */
+#define CD180_TBPRL     0x3a    /* Transmit Bit Rate Period Register Low  */
+#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_MCR       0x12    /* Modem Change Register                   */
+#define CD180_MCOR1     0x10    /* Modem Change Option 1 Register          */
+#define CD180_MCOR2     0x11    /* Modem Change Option 2 Register          */
+#define CD180_MSVR      0x28    /* Modem Signal Value Register             */
+#define CD180_MSVRTS    0x29    /* Modem Signal Value RTS                  */
+#define CD180_MSVDTR    0x2a    /* Modem Signal Value DTR                  */
+
+/* Global Interrupt Vector Register (R/W) */
+
+#define GSVR_ITMASK     0x07     /* Interrupt type mask                     */
+#define  GSVR_IT_MDM     0x01    /* Modem Signal Change Interrupt           */
+#define  GSVR_IT_TX      0x02    /* Transmit Data Interrupt                 */
+#define  GSVR_IT_RGD     0x03    /* Receive Good Data Interrupt             */
+#define  GSVR_IT_REXC    0x07    /* Receive Exception Interrupt             */
+
+
+/* Global Interrupt Channel Register (R/W) */
+ 
+#define GSCR_CHAN       0x1c    /* Channel Number Mask                     */
+#define GSCR_CHAN_OFF   2       /* Channel Number Offset                   */
+
+
+/* Channel Address Register (R/W) */
+
+#define CAR_CHAN        0x07    /* Channel Number Mask                     */
+
+
+/* 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                        */
+
+
+/* Service Request Enable Register (R/W) */
+
+#define SRER_DSR         0x80    /* Enable interrupt on DSR change          */
+#define SRER_CD          0x40    /* Enable interrupt on CD change           */
+#define SRER_CTS         0x20    /* Enable interrupt on CTS change          */
+#define SRER_RXD         0x10    /* Enable interrupt on Receive Data        */
+#define SRER_RXSC        0x08    /* Enable interrupt on Receive Spec. Char  */
+#define SRER_TXRDY       0x04    /* Enable interrupt on TX FIFO empty       */
+#define SRER_TXEMPTY     0x02    /* Enable interrupt on TX completely empty */
+#define SRER_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             */
+
+
+/* Service Request Status Register */
+
+#define SRSR_CMASK	0xC0	/* Current Service Context Mask            */
+#define  SRSR_CNONE	0x00	/* Not in a service context		   */
+#define  SRSR_CRX	0x40	/* Rx Context				   */
+#define  SRSR_CTX	0x80	/* Tx Context				   */
+#define  SRSR_CMDM	0xC0	/* Modem Context			   */
+#define SRSR_ANYINT	0x6F	/* Any interrupt flag			   */
+#define SRSR_RINT	0x10	/* Receive Interrupt			   */
+#define SRSR_TINT	0x04	/* Transmit Interrupt			   */
+#define SRSR_MINT	0x01	/* Modem Interrupt			   */
+#define SRSR_REXT	0x20	/* Receive External Interrupt		   */
+#define SRSR_TEXT	0x08	/* Transmit External Interrupt		   */
+#define SRSR_MEXT	0x02	/* Modem External Interrupt		   */
+
+
+/* Service Request Configuration Register */
+
+#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
+
+/* Values for register-based Interrupt ACKs */
+#define CD180_ACK_MINT	0x75	/* goes to MSMR				   */
+#define CD180_ACK_TINT	0x76	/* goes to TSMR				   */
+#define CD180_ACK_RINT	0x77	/* goes to RSMR				   */
+
+/* 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/sbus/char/cpwatchdog.c b/drivers/sbus/char/cpwatchdog.c
new file mode 100644
index 0000000..c82abeb
--- /dev/null
+++ b/drivers/sbus/char/cpwatchdog.c
@@ -0,0 +1,832 @@
+/* cpwatchdog.c - driver implementation for hardware watchdog
+ * timers found on Sun Microsystems CP1400 and CP1500 boards.
+ *
+ * This device supports both the generic Linux watchdog 
+ * interface and Solaris-compatible ioctls as best it is
+ * able.
+ *
+ * NOTE: 	CP1400 systems appear to have a defective intr_mask
+ * 			register on the PLD, preventing the disabling of
+ * 			timer interrupts.  We use a timer to periodically 
+ * 			reset 'stopped' watchdogs on affected platforms.
+ *
+ * TODO:	DevFS support (/dev/watchdogs/0 ... /dev/watchdogs/2)
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/timer.h>
+#include <asm/irq.h>
+#include <asm/ebus.h>
+#include <asm/oplib.h>
+#include <asm/uaccess.h>
+
+#include <asm/watchdog.h>
+
+#define WD_OBPNAME	"watchdog"
+#define WD_BADMODEL "SUNW,501-5336"
+#define WD_BTIMEOUT	(jiffies + (HZ * 1000))
+#define WD_BLIMIT	0xFFFF
+
+#define WD0_DEVNAME "watchdog0"
+#define WD1_DEVNAME "watchdog1"
+#define WD2_DEVNAME "watchdog2"
+
+#define WD0_MINOR	212
+#define WD1_MINOR	213	
+#define WD2_MINOR	214	
+
+
+/* Internal driver definitions
+ */
+#define WD0_ID			0		/* Watchdog0						*/
+#define WD1_ID			1		/* Watchdog1						*/
+#define WD2_ID			2		/* Watchdog2						*/
+#define WD_NUMDEVS		3		/* Device contains 3 timers			*/
+
+#define WD_INTR_OFF		0		/* Interrupt disable value			*/
+#define WD_INTR_ON		1		/* Interrupt enable value			*/
+
+#define WD_STAT_INIT	0x01	/* Watchdog timer is initialized	*/
+#define WD_STAT_BSTOP	0x02	/* Watchdog timer is brokenstopped	*/
+#define WD_STAT_SVCD	0x04	/* Watchdog interrupt occurred		*/
+
+/* Register value definitions
+ */
+#define WD0_INTR_MASK	0x01	/* Watchdog device interrupt masks	*/
+#define WD1_INTR_MASK	0x02
+#define WD2_INTR_MASK	0x04
+
+#define WD_S_RUNNING	0x01	/* Watchdog device status running	*/
+#define WD_S_EXPIRED	0x02	/* Watchdog device status expired	*/
+
+/* Sun uses Altera PLD EPF8820ATC144-4 
+ * providing three hardware watchdogs:
+ *
+ * 	1) RIC - sends an interrupt when triggered
+ * 	2) XIR - asserts XIR_B_RESET when triggered, resets CPU
+ * 	3) POR - asserts POR_B_RESET when triggered, resets CPU, backplane, board
+ *
+ *** Timer register block definition (struct wd_timer_regblk)
+ *
+ * dcntr and limit registers (halfword access):      
+ * -------------------
+ * | 15 | ...| 1 | 0 |
+ * -------------------
+ * |-  counter val  -|
+ * -------------------
+ * dcntr - 	Current 16-bit downcounter value.
+ * 			When downcounter reaches '0' watchdog expires.
+ * 			Reading this register resets downcounter with 'limit' value.
+ * limit - 	16-bit countdown value in 1/10th second increments.
+ * 			Writing this register begins countdown with input value.
+ * 			Reading from this register does not affect counter.
+ * NOTES:	After watchdog reset, dcntr and limit contain '1'
+ *
+ * status register (byte access):
+ * ---------------------------
+ * | 7 | ... | 2 |  1  |  0  |
+ * --------------+------------
+ * |-   UNUSED  -| EXP | RUN |
+ * ---------------------------
+ * status-	Bit 0 - Watchdog is running
+ * 			Bit 1 - Watchdog has expired
+ *
+ *** PLD register block definition (struct wd_pld_regblk)
+ *
+ * intr_mask register (byte access):
+ * ---------------------------------
+ * | 7 | ... | 3 |  2  |  1  |  0  |
+ * +-------------+------------------
+ * |-   UNUSED  -| WD3 | WD2 | WD1 |
+ * ---------------------------------
+ * WD3 -  1 == Interrupt disabled for watchdog 3
+ * WD2 -  1 == Interrupt disabled for watchdog 2
+ * WD1 -  1 == Interrupt disabled for watchdog 1
+ *
+ * pld_status register (byte access):
+ * UNKNOWN, MAGICAL MYSTERY REGISTER
+ *
+ */
+#define WD_TIMER_REGSZ	16
+#define WD0_OFF		0
+#define WD1_OFF		(WD_TIMER_REGSZ * 1)
+#define WD2_OFF		(WD_TIMER_REGSZ * 2)
+#define PLD_OFF		(WD_TIMER_REGSZ * 3)
+
+#define WD_DCNTR	0x00
+#define WD_LIMIT	0x04
+#define WD_STATUS	0x08
+
+#define PLD_IMASK	(PLD_OFF + 0x00)
+#define PLD_STATUS	(PLD_OFF + 0x04)
+
+/* Individual timer structure 
+ */
+struct wd_timer {
+	__u16			timeout;
+	__u8			intr_mask;
+	unsigned char		runstatus;
+	void __iomem		*regs;
+};
+
+/* Device structure
+ */
+struct wd_device {
+	int				irq;
+	spinlock_t		lock;
+	unsigned char	isbaddoggie;	/* defective PLD */
+	unsigned char	opt_enable;
+	unsigned char	opt_reboot;
+	unsigned short	opt_timeout;
+	unsigned char	initialized;
+	struct wd_timer	watchdog[WD_NUMDEVS];
+	void __iomem	*regs;
+};
+
+static struct wd_device wd_dev = { 
+		0, SPIN_LOCK_UNLOCKED, 0, 0, 0, 0,
+};
+
+static struct timer_list wd_timer;
+
+static int wd0_timeout = 0;
+static int wd1_timeout = 0;
+static int wd2_timeout = 0;
+
+#ifdef MODULE
+module_param	(wd0_timeout, int, 0);
+MODULE_PARM_DESC(wd0_timeout, "Default watchdog0 timeout in 1/10secs");
+module_param 	(wd1_timeout, int, 0);
+MODULE_PARM_DESC(wd1_timeout, "Default watchdog1 timeout in 1/10secs");
+module_param 	(wd2_timeout, int, 0);
+MODULE_PARM_DESC(wd2_timeout, "Default watchdog2 timeout in 1/10secs");
+
+MODULE_AUTHOR
+	("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION
+	("Hardware watchdog driver for Sun Microsystems CP1400/1500");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE
+	("watchdog");
+#endif /* ifdef MODULE */
+
+/* Forward declarations of internal methods
+ */
+#ifdef WD_DEBUG
+static void wd_dumpregs(void);
+#endif
+static irqreturn_t wd_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+static void wd_toggleintr(struct wd_timer* pTimer, int enable);
+static void wd_pingtimer(struct wd_timer* pTimer);
+static void wd_starttimer(struct wd_timer* pTimer);
+static void wd_resetbrokentimer(struct wd_timer* pTimer);
+static void wd_stoptimer(struct wd_timer* pTimer);
+static void wd_brokentimer(unsigned long data);
+static int  wd_getstatus(struct wd_timer* pTimer);
+
+/* PLD expects words to be written in LSB format,
+ * so we must flip all words prior to writing them to regs
+ */
+static inline unsigned short flip_word(unsigned short word)
+{
+	return ((word & 0xff) << 8) | ((word >> 8) & 0xff);
+}
+
+#define wd_writew(val, addr) 	(writew(flip_word(val), addr))
+#define wd_readw(addr) 			(flip_word(readw(addr)))
+#define wd_writeb(val, addr) 	(writeb(val, addr))
+#define wd_readb(addr) 			(readb(addr))
+
+
+/* CP1400s seem to have broken PLD implementations--
+ * the interrupt_mask register cannot be written, so
+ * no timer interrupts can be masked within the PLD.
+ */
+static inline int wd_isbroken(void)
+{
+	/* we could test this by read/write/read/restore
+	 * on the interrupt mask register only if OBP
+	 * 'watchdog-enable?' == FALSE, but it seems 
+	 * ubiquitous on CP1400s
+	 */
+	char val[32];
+	prom_getproperty(prom_root_node, "model", val, sizeof(val));
+	return((!strcmp(val, WD_BADMODEL)) ? 1 : 0);
+}
+		
+/* Retrieve watchdog-enable? option from OBP
+ * Returns 0 if false, 1 if true
+ */
+static inline int wd_opt_enable(void)
+{
+	int opt_node;
+
+	opt_node = prom_getchild(prom_root_node);
+	opt_node = prom_searchsiblings(opt_node, "options");
+	return((-1 == prom_getint(opt_node, "watchdog-enable?")) ? 0 : 1);
+}
+
+/* Retrieve watchdog-reboot? option from OBP
+ * Returns 0 if false, 1 if true
+ */
+static inline int wd_opt_reboot(void)
+{
+	int opt_node;
+
+	opt_node = prom_getchild(prom_root_node);
+	opt_node = prom_searchsiblings(opt_node, "options");
+	return((-1 == prom_getint(opt_node, "watchdog-reboot?")) ? 0 : 1);
+}
+
+/* Retrieve watchdog-timeout option from OBP
+ * Returns OBP value, or 0 if not located
+ */
+static inline int wd_opt_timeout(void)
+{
+	int opt_node;
+	char value[32];
+	char *p = value;
+
+	opt_node = prom_getchild(prom_root_node);
+	opt_node = prom_searchsiblings(opt_node, "options");
+	opt_node = prom_getproperty(opt_node, 
+								"watchdog-timeout", 
+								value, 
+								sizeof(value));
+	if(-1 != opt_node) {
+		/* atoi implementation */
+		for(opt_node = 0; /* nop */; p++) {
+			if(*p >= '0' && *p <= '9') {
+				opt_node = (10*opt_node)+(*p-'0');
+			}
+			else {
+				break;
+			}
+		}
+	}
+	return((-1 == opt_node) ? (0) : (opt_node)); 
+}
+
+static int wd_open(struct inode *inode, struct file *f)
+{
+	switch(iminor(inode))
+	{
+		case WD0_MINOR:
+			f->private_data = &wd_dev.watchdog[WD0_ID];
+			break;
+		case WD1_MINOR:
+			f->private_data = &wd_dev.watchdog[WD1_ID];
+			break;
+		case WD2_MINOR:
+			f->private_data = &wd_dev.watchdog[WD2_ID];
+			break;
+		default:
+			return(-ENODEV);
+	}
+
+	/* Register IRQ on first open of device */
+	if(0 == wd_dev.initialized)
+	{	
+		if (request_irq(wd_dev.irq, 
+						&wd_interrupt, 
+						SA_SHIRQ,
+						WD_OBPNAME,
+						(void *)wd_dev.regs)) {
+			printk("%s: Cannot register IRQ %s\n", 
+				WD_OBPNAME, __irq_itoa(wd_dev.irq));
+			return(-EBUSY);
+		}
+		wd_dev.initialized = 1;
+	}
+
+	return(nonseekable_open(inode, f));
+}
+
+static int wd_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static int wd_ioctl(struct inode *inode, struct file *file, 
+		     unsigned int cmd, unsigned long arg)
+{
+	int 	setopt 				= 0;
+	struct 	wd_timer* pTimer 	= (struct wd_timer*)file->private_data;
+	void __user *argp = (void __user *)arg;
+	struct 	watchdog_info info 	= {
+		0,
+		0,
+		"Altera EPF8820ATC144-4"
+	};
+
+	if(NULL == pTimer) {
+		return(-EINVAL);
+	}
+
+	switch(cmd)
+	{
+		/* Generic Linux IOCTLs */
+		case WDIOC_GETSUPPORT:
+			if(copy_to_user(argp, &info, sizeof(struct watchdog_info))) {
+				return(-EFAULT);
+			}
+			break;
+		case WDIOC_GETSTATUS:
+		case WDIOC_GETBOOTSTATUS:
+			if (put_user(0, (int __user *)argp))
+				return -EFAULT;
+			break;
+		case WDIOC_KEEPALIVE:
+			wd_pingtimer(pTimer);
+			break;
+		case WDIOC_SETOPTIONS:
+			if(copy_from_user(&setopt, argp, sizeof(unsigned int))) {
+				return -EFAULT;
+			}
+			if(setopt & WDIOS_DISABLECARD) {
+				if(wd_dev.opt_enable) {
+					printk(
+						"%s: cannot disable watchdog in ENABLED mode\n",
+						WD_OBPNAME);
+					return(-EINVAL);
+				}
+				wd_stoptimer(pTimer);
+			}
+			else if(setopt & WDIOS_ENABLECARD) {
+				wd_starttimer(pTimer);
+			}
+			else {
+				return(-EINVAL);
+			}	
+			break;
+		/* Solaris-compatible IOCTLs */
+		case WIOCGSTAT:
+			setopt = wd_getstatus(pTimer);
+			if(copy_to_user(argp, &setopt, sizeof(unsigned int))) {
+				return(-EFAULT);
+			}
+			break;
+		case WIOCSTART:
+			wd_starttimer(pTimer);
+			break;
+		case WIOCSTOP:
+			if(wd_dev.opt_enable) {
+				printk("%s: cannot disable watchdog in ENABLED mode\n",
+					WD_OBPNAME);
+				return(-EINVAL);
+			}
+			wd_stoptimer(pTimer);
+			break;
+		default:
+			return(-EINVAL);
+	}
+	return(0);
+}
+
+static ssize_t wd_write(struct file 	*file, 
+			const char	__user *buf, 
+			size_t 		count, 
+			loff_t 		*ppos)
+{
+	struct wd_timer* pTimer = (struct wd_timer*)file->private_data;
+
+	if(NULL == pTimer) {
+		return(-EINVAL);
+	}
+
+	if (count) {
+		wd_pingtimer(pTimer);
+		return 1;
+	}
+	return 0;
+}
+
+static ssize_t wd_read(struct file * file, char __user *buffer,
+		        size_t count, loff_t *ppos)
+{
+#ifdef WD_DEBUG
+	wd_dumpregs();
+	return(0);
+#else
+	return(-EINVAL);
+#endif /* ifdef WD_DEBUG */
+}
+
+static irqreturn_t wd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/* Only WD0 will interrupt-- others are NMI and we won't
+	 * see them here....
+	 */
+	spin_lock_irq(&wd_dev.lock);
+	if((unsigned long)wd_dev.regs == (unsigned long)dev_id)
+	{
+		wd_stoptimer(&wd_dev.watchdog[WD0_ID]);
+		wd_dev.watchdog[WD0_ID].runstatus |=  WD_STAT_SVCD;
+	}
+	spin_unlock_irq(&wd_dev.lock);
+	return IRQ_HANDLED;
+}
+
+static struct file_operations wd_fops = {
+	.owner =	THIS_MODULE,
+	.ioctl =	wd_ioctl,
+	.open =		wd_open,
+	.write =	wd_write,
+	.read =		wd_read,
+	.release =	wd_release,
+};
+
+static struct miscdevice wd0_miscdev = { WD0_MINOR, WD0_DEVNAME, &wd_fops };
+static struct miscdevice wd1_miscdev = { WD1_MINOR, WD1_DEVNAME, &wd_fops };
+static struct miscdevice wd2_miscdev = { WD2_MINOR, WD2_DEVNAME, &wd_fops };
+
+#ifdef WD_DEBUG
+static void wd_dumpregs(void)
+{
+	/* Reading from downcounters initiates watchdog countdown--
+	 * Example is included below for illustration purposes.
+	 */
+	int i;
+	printk("%s: dumping register values\n", WD_OBPNAME);
+	for(i = WD0_ID; i < WD_NUMDEVS; ++i) {
+			/* printk("\t%s%i: dcntr  at 0x%lx: 0x%x\n", 
+			 * 	WD_OBPNAME,
+		 	 *	i,
+			 *	(unsigned long)(&wd_dev.watchdog[i].regs->dcntr), 
+			 *	readw(&wd_dev.watchdog[i].regs->dcntr));
+			 */
+			printk("\t%s%i: limit  at 0x%lx: 0x%x\n", 
+				WD_OBPNAME,
+				i,
+				(unsigned long)(&wd_dev.watchdog[i].regs->limit), 
+				readw(&wd_dev.watchdog[i].regs->limit));
+			printk("\t%s%i: status at 0x%lx: 0x%x\n", 
+				WD_OBPNAME,
+				i,
+				(unsigned long)(&wd_dev.watchdog[i].regs->status), 
+				readb(&wd_dev.watchdog[i].regs->status));
+			printk("\t%s%i: driver status: 0x%x\n",
+				WD_OBPNAME,
+				i,
+				wd_getstatus(&wd_dev.watchdog[i]));
+	}
+	printk("\tintr_mask  at %p: 0x%x\n", 
+		wd_dev.regs + PLD_IMASK,
+		readb(wd_dev.regs + PLD_IMASK));
+	printk("\tpld_status at %p: 0x%x\n", 
+		wd_dev.regs + PLD_STATUS, 
+		readb(wd_dev.regs + PLD_STATUS));
+}
+#endif
+
+/* Enable or disable watchdog interrupts
+ * Because of the CP1400 defect this should only be
+ * called during initialzation or by wd_[start|stop]timer()
+ *
+ * pTimer 	- pointer to timer device, or NULL to indicate all timers 
+ * enable	- non-zero to enable interrupts, zero to disable
+ */
+static void wd_toggleintr(struct wd_timer* pTimer, int enable)
+{
+	unsigned char curregs = wd_readb(wd_dev.regs + PLD_IMASK);
+	unsigned char setregs = 
+		(NULL == pTimer) ? 
+			(WD0_INTR_MASK | WD1_INTR_MASK | WD2_INTR_MASK) : 
+			(pTimer->intr_mask);
+
+	(WD_INTR_ON == enable) ?
+		(curregs &= ~setregs):
+		(curregs |=  setregs);
+
+	wd_writeb(curregs, wd_dev.regs + PLD_IMASK);
+	return;
+}
+
+/* Reset countdown timer with 'limit' value and continue countdown.
+ * This will not start a stopped timer.
+ *
+ * pTimer	- pointer to timer device
+ */
+static void wd_pingtimer(struct wd_timer* pTimer)
+{
+	if (wd_readb(pTimer->regs + WD_STATUS) & WD_S_RUNNING) {
+		wd_readw(pTimer->regs + WD_DCNTR);
+	}
+}
+
+/* Stop a running watchdog timer-- the timer actually keeps
+ * running, but the interrupt is masked so that no action is
+ * taken upon expiration.
+ *
+ * pTimer	- pointer to timer device
+ */
+static void wd_stoptimer(struct wd_timer* pTimer)
+{
+	if(wd_readb(pTimer->regs + WD_STATUS) & WD_S_RUNNING) {
+		wd_toggleintr(pTimer, WD_INTR_OFF);
+
+		if(wd_dev.isbaddoggie) {
+			pTimer->runstatus |= WD_STAT_BSTOP;
+			wd_brokentimer((unsigned long)&wd_dev);
+		}
+	}
+}
+
+/* Start a watchdog timer with the specified limit value
+ * If the watchdog is running, it will be restarted with
+ * the provided limit value.
+ *
+ * This function will enable interrupts on the specified
+ * watchdog.
+ *
+ * pTimer	- pointer to timer device
+ * limit	- limit (countdown) value in 1/10th seconds
+ */
+static void wd_starttimer(struct wd_timer* pTimer)
+{
+	if(wd_dev.isbaddoggie) {
+		pTimer->runstatus &= ~WD_STAT_BSTOP;
+	}
+	pTimer->runstatus &= ~WD_STAT_SVCD;
+
+	wd_writew(pTimer->timeout, pTimer->regs + WD_LIMIT);
+	wd_toggleintr(pTimer, WD_INTR_ON);
+}
+
+/* Restarts timer with maximum limit value and
+ * does not unset 'brokenstop' value.
+ */
+static void wd_resetbrokentimer(struct wd_timer* pTimer)
+{
+	wd_toggleintr(pTimer, WD_INTR_ON);
+	wd_writew(WD_BLIMIT, pTimer->regs + WD_LIMIT);
+}
+
+/* Timer device initialization helper.
+ * Returns 0 on success, other on failure
+ */
+static int wd_inittimer(int whichdog)
+{
+	struct miscdevice 				*whichmisc;
+	void __iomem *whichregs;
+	char 							whichident[8];
+	int								whichmask;
+	__u16							whichlimit;
+
+	switch(whichdog)
+	{
+		case WD0_ID:
+			whichmisc = &wd0_miscdev;
+			strcpy(whichident, "RIC");
+			whichregs = wd_dev.regs + WD0_OFF;
+			whichmask = WD0_INTR_MASK;
+			whichlimit= (0 == wd0_timeout) 	? 
+						(wd_dev.opt_timeout): 
+						(wd0_timeout);
+			break;
+		case WD1_ID:
+			whichmisc = &wd1_miscdev;
+			strcpy(whichident, "XIR");
+			whichregs = wd_dev.regs + WD1_OFF;
+			whichmask = WD1_INTR_MASK;
+			whichlimit= (0 == wd1_timeout) 	? 
+						(wd_dev.opt_timeout): 
+						(wd1_timeout);
+			break;
+		case WD2_ID:
+			whichmisc = &wd2_miscdev;
+			strcpy(whichident, "POR");
+			whichregs = wd_dev.regs + WD2_OFF;
+			whichmask = WD2_INTR_MASK;
+			whichlimit= (0 == wd2_timeout) 	? 
+						(wd_dev.opt_timeout): 
+						(wd2_timeout);
+			break;
+		default:
+			printk("%s: %s: invalid watchdog id: %i\n",
+				WD_OBPNAME, __FUNCTION__, whichdog);
+			return(1);
+	}
+	if(0 != misc_register(whichmisc))
+	{
+		return(1);
+	}
+	wd_dev.watchdog[whichdog].regs			= whichregs;
+	wd_dev.watchdog[whichdog].timeout 		= whichlimit;
+	wd_dev.watchdog[whichdog].intr_mask		= whichmask;
+	wd_dev.watchdog[whichdog].runstatus 	&= ~WD_STAT_BSTOP;
+	wd_dev.watchdog[whichdog].runstatus 	|= WD_STAT_INIT;
+
+	printk("%s%i: %s hardware watchdog [%01i.%i sec] %s\n", 
+		WD_OBPNAME, 
+		whichdog, 
+		whichident, 
+		wd_dev.watchdog[whichdog].timeout / 10,
+		wd_dev.watchdog[whichdog].timeout % 10,
+		(0 != wd_dev.opt_enable) ? "in ENABLED mode" : "");
+	return(0);
+}
+
+/* Timer method called to reset stopped watchdogs--
+ * because of the PLD bug on CP1400, we cannot mask
+ * interrupts within the PLD so me must continually
+ * reset the timers ad infinitum.
+ */
+static void wd_brokentimer(unsigned long data)
+{
+	struct wd_device* pDev = (struct wd_device*)data;
+	int id, tripped = 0;
+
+	/* kill a running timer instance, in case we
+	 * were called directly instead of by kernel timer
+	 */
+	if(timer_pending(&wd_timer)) {
+		del_timer(&wd_timer);
+	}
+
+	for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+		if(pDev->watchdog[id].runstatus & WD_STAT_BSTOP) {
+			++tripped;
+			wd_resetbrokentimer(&pDev->watchdog[id]);
+		}
+	}
+
+	if(tripped) {
+		/* there is at least one timer brokenstopped-- reschedule */
+		init_timer(&wd_timer);
+		wd_timer.expires = WD_BTIMEOUT;
+		add_timer(&wd_timer);
+	}
+}
+
+static int wd_getstatus(struct wd_timer* pTimer)
+{
+	unsigned char stat = wd_readb(pTimer->regs + WD_STATUS);
+	unsigned char intr = wd_readb(wd_dev.regs + PLD_IMASK);
+	unsigned char ret  = WD_STOPPED;
+
+	/* determine STOPPED */
+	if(0 == stat ) { 
+		return(ret);
+	}
+	/* determine EXPIRED vs FREERUN vs RUNNING */
+	else if(WD_S_EXPIRED & stat) {
+		ret = WD_EXPIRED;
+	}
+	else if(WD_S_RUNNING & stat) {
+		if(intr & pTimer->intr_mask) {
+			ret = WD_FREERUN;
+		}
+		else {
+			/* Fudge WD_EXPIRED status for defective CP1400--
+			 * IF timer is running 
+			 * 	AND brokenstop is set 
+			 * 	AND an interrupt has been serviced
+			 * we are WD_EXPIRED.
+			 *
+			 * IF timer is running 
+			 * 	AND brokenstop is set 
+			 * 	AND no interrupt has been serviced
+			 * we are WD_FREERUN.
+			 */
+			if(wd_dev.isbaddoggie && (pTimer->runstatus & WD_STAT_BSTOP)) {
+				if(pTimer->runstatus & WD_STAT_SVCD) {
+					ret = WD_EXPIRED;
+				}
+				else {
+					/* we could as well pretend we are expired */
+					ret = WD_FREERUN;
+				}
+			}
+			else {
+				ret = WD_RUNNING;
+			}
+		}
+	}
+
+	/* determine SERVICED */
+	if(pTimer->runstatus & WD_STAT_SVCD) {
+		ret |= WD_SERVICED;
+	}
+
+	return(ret);
+}
+
+static int __init wd_init(void)
+{
+	int 	id;
+	struct 	linux_ebus *ebus = NULL;
+	struct 	linux_ebus_device *edev = NULL;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, WD_OBPNAME))
+				goto ebus_done;
+		}
+	}
+
+ebus_done:
+	if(!edev) {
+		printk("%s: unable to locate device\n", WD_OBPNAME);
+		return -ENODEV;
+	}
+
+	wd_dev.regs = 
+		ioremap(edev->resource[0].start, 4 * WD_TIMER_REGSZ); /* ? */
+
+	if(NULL == wd_dev.regs) {
+		printk("%s: unable to map registers\n", WD_OBPNAME);
+		return(-ENODEV);
+	}
+
+	/* initialize device structure from OBP parameters */
+	wd_dev.irq 			= edev->irqs[0];
+	wd_dev.opt_enable	= wd_opt_enable();
+	wd_dev.opt_reboot	= wd_opt_reboot();
+	wd_dev.opt_timeout	= wd_opt_timeout();
+	wd_dev.isbaddoggie	= wd_isbroken();
+
+	/* disable all interrupts unless watchdog-enabled? == true */
+	if(! wd_dev.opt_enable) {
+		wd_toggleintr(NULL, WD_INTR_OFF);
+	}
+
+	/* register miscellaneous devices */
+	for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+		if(0 != wd_inittimer(id)) {
+			printk("%s%i: unable to initialize\n", WD_OBPNAME, id);
+		}
+	}
+
+	/* warn about possible defective PLD */
+	if(wd_dev.isbaddoggie) {
+		init_timer(&wd_timer);
+		wd_timer.function 	= wd_brokentimer;
+		wd_timer.data		= (unsigned long)&wd_dev;
+		wd_timer.expires	= WD_BTIMEOUT;
+
+		printk("%s: PLD defect workaround enabled for model %s\n",
+			WD_OBPNAME, WD_BADMODEL);
+	}
+	return(0);
+}
+
+static void __exit wd_cleanup(void)
+{
+	int id;
+
+	/* if 'watchdog-enable?' == TRUE, timers are not stopped 
+	 * when module is unloaded.  All brokenstopped timers will
+	 * also now eventually trip. 
+	 */
+	for(id = WD0_ID; id < WD_NUMDEVS; ++id) {
+		if(WD_S_RUNNING == wd_readb(wd_dev.watchdog[id].regs + WD_STATUS)) {
+			if(wd_dev.opt_enable) {
+				printk(KERN_WARNING "%s%i: timer not stopped at release\n",
+					WD_OBPNAME, id);
+			}
+			else {
+				wd_stoptimer(&wd_dev.watchdog[id]);
+				if(wd_dev.watchdog[id].runstatus & WD_STAT_BSTOP) {
+					wd_resetbrokentimer(&wd_dev.watchdog[id]);
+					printk(KERN_WARNING 
+							"%s%i: defect workaround disabled at release, "\
+							"timer expires in ~%01i sec\n",
+							WD_OBPNAME, id, 
+							wd_readw(wd_dev.watchdog[id].regs + WD_LIMIT) / 10);
+				}
+			}
+		}
+	}
+
+	if(wd_dev.isbaddoggie && timer_pending(&wd_timer)) {
+		del_timer(&wd_timer);
+	}
+	if(0 != (wd_dev.watchdog[WD0_ID].runstatus & WD_STAT_INIT)) {
+		misc_deregister(&wd0_miscdev);
+	}
+	if(0 != (wd_dev.watchdog[WD1_ID].runstatus & WD_STAT_INIT)) {
+		misc_deregister(&wd1_miscdev);
+	}
+	if(0 != (wd_dev.watchdog[WD2_ID].runstatus & WD_STAT_INIT)) {
+		misc_deregister(&wd2_miscdev);
+	}
+	if(0 != wd_dev.initialized) {
+		free_irq(wd_dev.irq, (void *)wd_dev.regs);
+	}
+	iounmap(wd_dev.regs);
+}
+
+module_init(wd_init);
+module_exit(wd_cleanup);
diff --git a/drivers/sbus/char/display7seg.c b/drivers/sbus/char/display7seg.c
new file mode 100644
index 0000000..dbad7f3
--- /dev/null
+++ b/drivers/sbus/char/display7seg.c
@@ -0,0 +1,234 @@
+/* $Id: display7seg.c,v 1.6 2002/01/08 16:00:16 davem Exp $
+ *
+ * display7seg - Driver implementation for the 7-segment display
+ * present on Sun Microsystems CP1400 and CP1500
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>		/* request_region, check_region */
+#include <asm/atomic.h>
+#include <asm/ebus.h>			/* EBus device					*/
+#include <asm/oplib.h>			/* OpenProm Library 			*/
+#include <asm/uaccess.h>		/* put_/get_user			*/
+
+#include <asm/display7seg.h>
+
+#define D7S_MINOR	193
+#define D7S_OBPNAME	"display7seg"
+#define D7S_DEVNAME "d7s"
+
+static int sol_compat = 0;		/* Solaris compatibility mode	*/
+
+#ifdef MODULE
+
+/* Solaris compatibility flag -
+ * The Solaris implementation omits support for several
+ * documented driver features (ref Sun doc 806-0180-03).  
+ * By default, this module supports the documented driver 
+ * abilities, rather than the Solaris implementation:
+ *
+ * 	1) Device ALWAYS reverts to OBP-specified FLIPPED mode
+ * 	   upon closure of device or module unload.
+ * 	2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of
+ * 	   FLIP bit
+ *
+ * If you wish the device to operate as under Solaris,
+ * omitting above features, set this parameter to non-zero.
+ */
+module_param
+	(sol_compat, int, 0);
+MODULE_PARM_DESC
+	(sol_compat, 
+	 "Disables documented functionality omitted from Solaris driver");
+
+MODULE_AUTHOR
+	("Eric Brower <ebrower@usa.net>");
+MODULE_DESCRIPTION
+	("7-Segment Display driver for Sun Microsystems CP1400/1500");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE
+	("d7s");
+#endif /* ifdef MODULE */
+
+/*
+ * Register block address- see header for details
+ * -----------------------------------------
+ * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 |
+ * -----------------------------------------
+ *
+ * DP 		- Toggles decimal point on/off 
+ * ALARM	- Toggles "Alarm" LED green/red
+ * FLIP		- Inverts display for upside-down mounted board
+ * bits 0-4	- 7-segment display contents
+ */
+static void __iomem* d7s_regs;
+
+static inline void d7s_free(void)
+{
+	iounmap(d7s_regs);
+}
+
+static inline int d7s_obpflipped(void)
+{
+	int opt_node;
+
+	opt_node = prom_getchild(prom_root_node);
+	opt_node = prom_searchsiblings(opt_node, "options");
+	return ((-1 != prom_getintdefault(opt_node, "d7s-flipped?", -1)) ? 0 : 1);
+}
+
+static atomic_t d7s_users = ATOMIC_INIT(0);
+
+static int d7s_open(struct inode *inode, struct file *f)
+{
+	if (D7S_MINOR != iminor(inode))
+		return -ENODEV;
+	atomic_inc(&d7s_users);
+	return 0;
+}
+
+static int d7s_release(struct inode *inode, struct file *f)
+{
+	/* Reset flipped state to OBP default only if
+	 * no other users have the device open and we
+	 * are not operating in solaris-compat mode
+	 */
+	if (atomic_dec_and_test(&d7s_users) && !sol_compat) {
+		int regval = 0;
+
+		regval = readb(d7s_regs);
+		(0 == d7s_obpflipped())	? 
+			writeb(regval |= D7S_FLIP,  d7s_regs): 
+			writeb(regval &= ~D7S_FLIP, d7s_regs);
+	}
+
+	return 0;
+}
+
+static int d7s_ioctl(struct inode *inode, struct file *f, 
+		     unsigned int cmd, unsigned long arg)
+{
+	__u8 regs = readb(d7s_regs);
+	__u8 ireg = 0;
+
+	if (D7S_MINOR != iminor(inode))
+		return -ENODEV;
+
+	switch (cmd) {
+	case D7SIOCWR:
+		/* assign device register values
+		 * we mask-out D7S_FLIP if in sol_compat mode
+		 */
+		if (get_user(ireg, (int __user *) arg))
+			return -EFAULT;
+		if (0 != sol_compat) {
+			(regs & D7S_FLIP) ? 
+				(ireg |= D7S_FLIP) : (ireg &= ~D7S_FLIP);
+		}
+		writeb(ireg, d7s_regs);
+		break;
+
+	case D7SIOCRD:
+		/* retrieve device register values
+		 * NOTE: Solaris implementation returns D7S_FLIP bit
+		 * as toggled by user, even though it does not honor it.
+		 * This driver will not misinform you about the state
+		 * of your hardware while in sol_compat mode
+		 */
+		if (put_user(regs, (int __user *) arg))
+			return -EFAULT;
+		break;
+
+	case D7SIOCTM:
+		/* toggle device mode-- flip display orientation */
+		(regs & D7S_FLIP) ? 
+			(regs &= ~D7S_FLIP) : (regs |= D7S_FLIP);
+		writeb(regs, d7s_regs);
+		break;
+	};
+
+	return 0;
+}
+
+static struct file_operations d7s_fops = {
+	.owner =	THIS_MODULE,
+	.ioctl =	d7s_ioctl,
+	.open =		d7s_open,
+	.release =	d7s_release,
+};
+
+static struct miscdevice d7s_miscdev = { D7S_MINOR, D7S_DEVNAME, &d7s_fops };
+
+static int __init d7s_init(void)
+{
+	struct linux_ebus *ebus = NULL;
+	struct linux_ebus_device *edev = NULL;
+	int iTmp = 0, regs = 0;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, D7S_OBPNAME))
+				goto ebus_done;
+		}
+	}
+
+ebus_done:
+	if(!edev) {
+		printk("%s: unable to locate device\n", D7S_DEVNAME);
+		return -ENODEV;
+	}
+
+	d7s_regs = ioremap(edev->resource[0].start, sizeof(__u8));
+
+	iTmp = misc_register(&d7s_miscdev);
+	if (0 != iTmp) {
+		printk("%s: unable to acquire miscdevice minor %i\n",
+		       D7S_DEVNAME, D7S_MINOR);
+		iounmap(d7s_regs);
+		return iTmp;
+	}
+
+	/* OBP option "d7s-flipped?" is honored as default
+	 * for the device, and reset default when detached
+	 */
+	regs = readb(d7s_regs);
+	iTmp = d7s_obpflipped();
+	(0 == iTmp) ? 
+		writeb(regs |= D7S_FLIP,  d7s_regs): 
+		writeb(regs &= ~D7S_FLIP, d7s_regs);
+
+	printk("%s: 7-Segment Display%s at 0x%lx %s\n", 
+	       D7S_DEVNAME,
+	       (0 == iTmp) ? (" (FLIPPED)") : (""),
+	       edev->resource[0].start,
+	       (0 != sol_compat) ? ("in sol_compat mode") : (""));
+
+	return 0;
+}
+
+static void __exit d7s_cleanup(void)
+{
+	int regs = readb(d7s_regs);
+
+	/* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
+	if (0 == sol_compat) {
+		(0 == d7s_obpflipped())	? 
+			writeb(regs |= D7S_FLIP,  d7s_regs):
+			writeb(regs &= ~D7S_FLIP, d7s_regs);
+	}
+
+	misc_deregister(&d7s_miscdev);
+	d7s_free();
+}
+
+module_init(d7s_init);
+module_exit(d7s_cleanup);
diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c
new file mode 100644
index 0000000..f6ed35b
--- /dev/null
+++ b/drivers/sbus/char/envctrl.c
@@ -0,0 +1,1181 @@
+/* $Id: envctrl.c,v 1.25 2002/01/15 09:01:26 davem Exp $
+ * envctrl.c: Temperature and Fan monitoring on Machines providing it.
+ *
+ * Copyright (C) 1998  Eddie C. Dost  (ecd@skynet.be)
+ * Copyright (C) 2000  Vinh Truong    (vinh.truong@eng.sun.com)
+ * VT - The implementation is to support Sun Microelectronics (SME) platform
+ *      environment monitoring.  SME platforms use pcf8584 as the i2c bus 
+ *      controller to access pcf8591 (8-bit A/D and D/A converter) and 
+ *      pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface).
+ *      At board level, it follows SME Firmware I2C Specification. Reference:
+ * 	http://www-eu2.semiconductors.com/pip/PCF8584P
+ * 	http://www-eu2.semiconductors.com/pip/PCF8574AP
+ * 	http://www-eu2.semiconductors.com/pip/PCF8591P
+ *
+ * EB - Added support for CP1500 Global Address and PS/Voltage monitoring.
+ * 		Eric Brower <ebrower@usa.net>
+ *
+ * DB - Audit every copy_to_user in envctrl_read.
+ *              Daniele Bellucci <bellucda@tiscali.it>
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include <asm/ebus.h>
+#include <asm/uaccess.h>
+#include <asm/envctrl.h>
+
+#define __KERNEL_SYSCALLS__
+static int errno;
+#include <asm/unistd.h>
+
+#define ENVCTRL_MINOR	162
+
+#define PCF8584_ADDRESS	0x55
+
+#define CONTROL_PIN	0x80
+#define CONTROL_ES0	0x40
+#define CONTROL_ES1	0x20
+#define CONTROL_ES2	0x10
+#define CONTROL_ENI	0x08
+#define CONTROL_STA	0x04
+#define CONTROL_STO	0x02
+#define CONTROL_ACK	0x01
+
+#define STATUS_PIN	0x80
+#define STATUS_STS	0x20
+#define STATUS_BER	0x10
+#define STATUS_LRB	0x08
+#define STATUS_AD0	0x08
+#define STATUS_AAB	0x04
+#define STATUS_LAB	0x02
+#define STATUS_BB	0x01
+
+/*
+ * CLK Mode Register.
+ */
+#define BUS_CLK_90	0x00
+#define BUS_CLK_45	0x01
+#define BUS_CLK_11	0x02
+#define BUS_CLK_1_5	0x03
+
+#define CLK_3		0x00
+#define CLK_4_43	0x10
+#define CLK_6		0x14
+#define CLK_8		0x18
+#define CLK_12		0x1c
+
+#define OBD_SEND_START	0xc5    /* value to generate I2c_bus START condition */
+#define OBD_SEND_STOP 	0xc3    /* value to generate I2c_bus STOP condition */
+
+/* Monitor type of i2c child device.
+ * Firmware definitions.
+ */
+#define PCF8584_MAX_CHANNELS            8
+#define PCF8584_GLOBALADDR_TYPE			6  /* global address monitor */
+#define PCF8584_FANSTAT_TYPE            3  /* fan status monitor */
+#define PCF8584_VOLTAGE_TYPE            2  /* voltage monitor    */
+#define PCF8584_TEMP_TYPE	        	1  /* temperature monitor*/
+
+/* Monitor type of i2c child device.
+ * Driver definitions.
+ */
+#define ENVCTRL_NOMON				0
+#define ENVCTRL_CPUTEMP_MON			1    /* cpu temperature monitor */
+#define ENVCTRL_CPUVOLTAGE_MON	  	2    /* voltage monitor         */
+#define ENVCTRL_FANSTAT_MON  		3    /* fan status monitor      */
+#define ENVCTRL_ETHERTEMP_MON		4    /* ethernet temperarture */
+					     /* monitor                     */
+#define ENVCTRL_VOLTAGESTAT_MON	  	5    /* voltage status monitor  */
+#define ENVCTRL_MTHRBDTEMP_MON		6    /* motherboard temperature */
+#define ENVCTRL_SCSITEMP_MON		7    /* scsi temperarture */
+#define ENVCTRL_GLOBALADDR_MON		8    /* global address */
+
+/* Child device type.
+ * Driver definitions.
+ */
+#define I2C_ADC				0    /* pcf8591 */
+#define I2C_GPIO			1    /* pcf8571 */
+
+/* Data read from child device may need to decode
+ * through a data table and a scale.
+ * Translation type as defined by firmware.
+ */
+#define ENVCTRL_TRANSLATE_NO		0
+#define ENVCTRL_TRANSLATE_PARTIAL	1
+#define ENVCTRL_TRANSLATE_COMBINED	2
+#define ENVCTRL_TRANSLATE_FULL		3     /* table[data] */
+#define ENVCTRL_TRANSLATE_SCALE		4     /* table[data]/scale */
+
+/* Driver miscellaneous definitions. */
+#define ENVCTRL_MAX_CPU			4
+#define CHANNEL_DESC_SZ			256
+
+/* Mask values for combined GlobalAddress/PowerStatus node */
+#define ENVCTRL_GLOBALADDR_ADDR_MASK 	0x1F
+#define ENVCTRL_GLOBALADDR_PSTAT_MASK	0x60
+
+/* Node 0x70 ignored on CompactPCI CP1400/1500 platforms 
+ * (see envctrl_init_i2c_child)
+ */
+#define ENVCTRL_CPCI_IGNORED_NODE		0x70
+
+#define PCF8584_DATA	0x00
+#define PCF8584_CSR	0x01
+
+/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS.
+ * Property of a port or channel as defined by the firmware.
+ */
+struct pcf8584_channel {
+        unsigned char chnl_no;
+        unsigned char io_direction;
+        unsigned char type;
+        unsigned char last;
+};
+
+/* Each child device may have one or more tables of bytes to help decode
+ * data. Table property as defined by the firmware.
+ */ 
+struct pcf8584_tblprop {
+        unsigned int type;
+        unsigned int scale;  
+        unsigned int offset; /* offset from the beginning of the table */
+        unsigned int size;
+};
+
+/* i2c child */
+struct i2c_child_t {
+	/* Either ADC or GPIO. */
+	unsigned char i2ctype;
+        unsigned long addr;    
+        struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS];
+
+	/* Channel info. */ 
+	unsigned int total_chnls;	/* Number of monitor channels. */
+	unsigned char fan_mask;		/* Byte mask for fan status channels. */
+	unsigned char voltage_mask;	/* Byte mask for voltage status channels. */
+        struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS];
+
+	/* Properties of all monitor channels. */
+	unsigned int total_tbls;	/* Number of monitor tables. */
+        char *tables;			/* Pointer to table(s). */
+	char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */
+	char mon_type[PCF8584_MAX_CHANNELS];
+};
+
+static void __iomem *i2c;
+static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2];
+static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 };
+static unsigned int warning_temperature = 0;
+static unsigned int shutdown_temperature = 0;
+static char read_cpu;
+
+/* Forward declarations. */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char);
+
+/* Function Description: Test the PIN bit (Pending Interrupt Not) 
+ * 			 to test when serial transmission is completed .
+ * Return : None.
+ */
+static void envtrl_i2c_test_pin(void)
+{
+	int limit = 1000000;
+
+	while (--limit > 0) {
+		if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN)) 
+			break;
+		udelay(1);
+	} 
+
+	if (limit <= 0)
+		printk(KERN_INFO "envctrl: Pin status will not clear.\n");
+}
+
+/* Function Description: Test busy bit.
+ * Return : None.
+ */
+static void envctrl_i2c_test_bb(void)
+{
+	int limit = 1000000;
+
+	while (--limit > 0) {
+		/* Busy bit 0 means busy. */
+		if (readb(i2c + PCF8584_CSR) & STATUS_BB)
+			break;
+		udelay(1);
+	} 
+
+	if (limit <= 0)
+		printk(KERN_INFO "envctrl: Busy bit will not clear.\n");
+}
+
+/* Function Description: Send the address for a read access.
+ * Return : 0 if not acknowledged, otherwise acknowledged.
+ */
+static int envctrl_i2c_read_addr(unsigned char addr)
+{
+	envctrl_i2c_test_bb();
+
+	/* Load address. */
+	writeb(addr + 1, i2c + PCF8584_DATA);
+
+	envctrl_i2c_test_bb();
+
+	writeb(OBD_SEND_START, i2c + PCF8584_CSR);
+
+	/* Wait for PIN. */
+	envtrl_i2c_test_pin();
+
+	/* CSR 0 means acknowledged. */
+	if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) {
+		return readb(i2c + PCF8584_DATA);
+	} else {
+		writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
+		return 0;
+	}
+}
+
+/* Function Description: Send the address for write mode.  
+ * Return : None.
+ */
+static void envctrl_i2c_write_addr(unsigned char addr)
+{
+	envctrl_i2c_test_bb();
+	writeb(addr, i2c + PCF8584_DATA);
+
+	/* Generate Start condition. */
+	writeb(OBD_SEND_START, i2c + PCF8584_CSR);
+}
+
+/* Function Description: Read 1 byte of data from addr 
+ *			 set by envctrl_i2c_read_addr() 
+ * Return : Data from address set by envctrl_i2c_read_addr().
+ */
+static unsigned char envctrl_i2c_read_data(void)
+{
+	envtrl_i2c_test_pin();
+	writeb(CONTROL_ES0, i2c + PCF8584_CSR);  /* Send neg ack. */
+	return readb(i2c + PCF8584_DATA);
+}
+
+/* Function Description: Instruct the device which port to read data from.  
+ * Return : None.
+ */
+static void envctrl_i2c_write_data(unsigned char port)
+{
+	envtrl_i2c_test_pin();
+	writeb(port, i2c + PCF8584_DATA);
+}
+
+/* Function Description: Generate Stop condition after last byte is sent.
+ * Return : None.
+ */
+static void envctrl_i2c_stop(void)
+{
+	envtrl_i2c_test_pin();
+	writeb(OBD_SEND_STOP, i2c + PCF8584_CSR);
+}
+
+/* Function Description: Read adc device.
+ * Return : Data at address and port.
+ */
+static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port)
+{
+	/* Send address. */
+	envctrl_i2c_write_addr(addr);
+
+	/* Setup port to read. */
+	envctrl_i2c_write_data(port);
+	envctrl_i2c_stop();
+
+	/* Read port. */
+	envctrl_i2c_read_addr(addr);
+
+	/* Do a single byte read and send stop. */
+	envctrl_i2c_read_data();
+	envctrl_i2c_stop();
+
+	return readb(i2c + PCF8584_DATA);
+}
+
+/* Function Description: Read gpio device.
+ * Return : Data at address.
+ */
+static unsigned char envctrl_i2c_read_8574(unsigned char addr)
+{
+	unsigned char rd;
+
+	envctrl_i2c_read_addr(addr);
+
+	/* Do a single byte read and send stop. */
+	rd = envctrl_i2c_read_data();
+	envctrl_i2c_stop();
+	return rd;
+}
+
+/* Function Description: Decode data read from an adc device using firmware
+ *                       table.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_i2c_data_translate(unsigned char data, int translate_type,
+				      int scale, char *tbl, char *bufdata)
+{
+	int len = 0;
+
+	switch (translate_type) {
+	case ENVCTRL_TRANSLATE_NO:
+		/* No decode necessary. */
+		len = 1;
+		bufdata[0] = data;
+		break;
+
+	case ENVCTRL_TRANSLATE_FULL:
+		/* Decode this way: data = table[data]. */
+		len = 1;
+		bufdata[0] = tbl[data];
+		break;
+
+	case ENVCTRL_TRANSLATE_SCALE:
+		/* Decode this way: data = table[data]/scale */
+		sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale));
+		len = strlen(bufdata);
+		bufdata[len - 1] = bufdata[len - 2];
+		bufdata[len - 2] = '.';
+		break;
+
+	default:
+		break;
+	};
+
+	return len;
+}
+
+/* Function Description: Read cpu-related data such as cpu temperature, voltage.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild,
+				 char mon_type, unsigned char *bufdata)
+{
+	unsigned char data;
+	int i;
+	char *tbl, j = -1;
+
+	/* Find the right monitor type and channel. */
+	for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+		if (pchild->mon_type[i] == mon_type) {
+			if (++j == cpu) {
+				break;
+			}
+		}
+	}
+
+	if (j != cpu)
+		return 0;
+
+        /* Read data from address and port. */
+	data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+				     (unsigned char)pchild->chnl_array[i].chnl_no);
+
+	/* Find decoding table. */
+	tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+	return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+					  pchild->tblprop_array[i].scale,
+					  tbl, bufdata);
+}
+
+/* Function Description: Read noncpu-related data such as motherboard 
+ *                       temperature.
+ * Return: Number of read bytes. Data is stored in bufdata in ascii format.
+ */
+static int envctrl_read_noncpu_info(struct i2c_child_t *pchild,
+				    char mon_type, unsigned char *bufdata)
+{
+	unsigned char data;
+	int i;
+	char *tbl = NULL;
+
+	for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+		if (pchild->mon_type[i] == mon_type)
+			break;
+	}
+
+	if (i >= PCF8584_MAX_CHANNELS)
+		return 0;
+
+        /* Read data from address and port. */
+	data = envctrl_i2c_read_8591((unsigned char)pchild->addr,
+				     (unsigned char)pchild->chnl_array[i].chnl_no);
+
+	/* Find decoding table. */
+	tbl = pchild->tables + pchild->tblprop_array[i].offset;
+
+	return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type,
+					  pchild->tblprop_array[i].scale,
+					  tbl, bufdata);
+}
+
+/* Function Description: Read fan status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static int envctrl_i2c_fan_status(struct i2c_child_t *pchild,
+				  unsigned char data,
+				  char *bufdata)
+{
+	unsigned char tmp, ret = 0;
+	int i, j = 0;
+
+	tmp = data & pchild->fan_mask;
+
+	if (tmp == pchild->fan_mask) {
+		/* All bits are on. All fans are functioning. */
+		ret = ENVCTRL_ALL_FANS_GOOD;
+	} else if (tmp == 0) {
+		/* No bits are on. No fans are functioning. */
+		ret = ENVCTRL_ALL_FANS_BAD;
+	} else {
+		/* Go through all channels, mark 'on' the matched bits.
+		 * Notice that fan_mask may have discontiguous bits but
+		 * return mask are always contiguous. For example if we
+		 * monitor 4 fans at channels 0,1,2,4, the return mask
+		 * should be 00010000 if only fan at channel 4 is working.
+		 */
+		for (i = 0; i < PCF8584_MAX_CHANNELS;i++) {
+			if (pchild->fan_mask & chnls_mask[i]) {
+				if (!(chnls_mask[i] & tmp))
+					ret |= chnls_mask[j];
+
+				j++;
+			}
+		}
+	}
+
+	bufdata[0] = ret;
+	return 1;
+}
+
+/* Function Description: Read global addressing line.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static int envctrl_i2c_globaladdr(struct i2c_child_t *pchild,
+				  unsigned char data,
+				  char *bufdata)
+{
+	/* Translatation table is not necessary, as global
+	 * addr is the integer value of the GA# bits.
+	 *
+	 * NOTE: MSB is documented as zero, but I see it as '1' always....
+	 *
+	 * -----------------------------------------------
+	 * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 |
+	 * -----------------------------------------------
+	 * GA0 - GA4	integer value of Global Address (backplane slot#)
+	 * DEG			0 = cPCI Power supply output is starting to degrade
+	 * 				1 = cPCI Power supply output is OK
+	 * FAL			0 = cPCI Power supply has failed
+	 * 				1 = cPCI Power supply output is OK
+	 */
+	bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK);
+	return 1;
+}
+
+/* Function Description: Read standard voltage and power supply status.
+ * Return : Always 1 byte. Status stored in bufdata.
+ */
+static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild,
+						unsigned char data,
+						char *bufdata)
+{
+	unsigned char tmp, ret = 0;
+	int i, j = 0;
+
+	tmp = data & pchild->voltage_mask;
+
+	/* Two channels are used to monitor voltage and power supply. */
+	if (tmp == pchild->voltage_mask) {
+		/* All bits are on. Voltage and power supply are okay. */
+		ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD;
+	} else if (tmp == 0) {
+		/* All bits are off. Voltage and power supply are bad */
+		ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD;
+	} else {
+		/* Either voltage or power supply has problem. */
+		for (i = 0; i < PCF8584_MAX_CHANNELS; i++) {
+			if (pchild->voltage_mask & chnls_mask[i]) {
+				j++;
+
+				/* Break out when there is a mismatch. */
+				if (!(chnls_mask[i] & tmp))
+					break; 
+			}
+		}
+
+		/* Make a wish that hardware will always use the
+		 * first channel for voltage and the second for
+		 * power supply.
+		 */
+		if (j == 1)
+			ret = ENVCTRL_VOLTAGE_BAD;
+		else
+			ret = ENVCTRL_POWERSUPPLY_BAD;
+	}
+
+	bufdata[0] = ret;
+	return 1;
+}
+
+/* Function Description: Read a byte from /dev/envctrl. Mapped to user read().
+ * Return: Number of read bytes. 0 for error.
+ */
+static ssize_t
+envctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
+{
+	struct i2c_child_t *pchild;
+	unsigned char data[10];
+	int ret = 0;
+
+	/* Get the type of read as decided in ioctl() call.
+	 * Find the appropriate i2c child.
+	 * Get the data and put back to the user buffer.
+	 */
+
+	switch ((int)(long)file->private_data) {
+	case ENVCTRL_RD_WARNING_TEMPERATURE:
+		if (warning_temperature == 0)
+			return 0;
+
+		data[0] = (unsigned char)(warning_temperature);
+		ret = 1;
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+		if (shutdown_temperature == 0)
+			return 0;
+
+		data[0] = (unsigned char)(shutdown_temperature);
+		ret = 1;
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON)))
+			return 0;
+		ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_CPU_TEMPERATURE:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON)))
+			return 0;
+		ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data);
+
+		/* Reset cpu to the default cpu0. */
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_CPU_VOLTAGE:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON)))
+			return 0;
+		ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data);
+
+		/* Reset cpu to the default cpu0. */
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_SCSI_TEMPERATURE:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON)))
+			return 0;
+		ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON)))
+			return 0;
+		ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_FAN_STATUS:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON)))
+			return 0;
+		data[0] = envctrl_i2c_read_8574(pchild->addr);
+		ret = envctrl_i2c_fan_status(pchild,data[0], data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+	
+	case ENVCTRL_RD_GLOBALADDRESS:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
+			return 0;
+		data[0] = envctrl_i2c_read_8574(pchild->addr);
+		ret = envctrl_i2c_globaladdr(pchild, data[0], data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	case ENVCTRL_RD_VOLTAGE_STATUS:
+		if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON)))
+			/* If voltage monitor not present, check for CPCI equivalent */
+			if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON)))
+				return 0;
+		data[0] = envctrl_i2c_read_8574(pchild->addr);
+		ret = envctrl_i2c_voltage_status(pchild, data[0], data);
+		if (copy_to_user(buf, data, ret))
+			ret = -EFAULT;
+		break;
+
+	default:
+		break;
+
+	};
+
+	return ret;
+}
+
+/* Function Description: Command what to read.  Mapped to user ioctl().
+ * Return: Gives 0 for implemented commands, -EINVAL otherwise.
+ */
+static int
+envctrl_ioctl(struct inode *inode, struct file *file,
+	      unsigned int cmd, unsigned long arg)
+{
+	char __user *infobuf;
+
+	switch (cmd) {
+	case ENVCTRL_RD_WARNING_TEMPERATURE:
+	case ENVCTRL_RD_SHUTDOWN_TEMPERATURE:
+	case ENVCTRL_RD_MTHRBD_TEMPERATURE:
+	case ENVCTRL_RD_FAN_STATUS:
+	case ENVCTRL_RD_VOLTAGE_STATUS:
+	case ENVCTRL_RD_ETHERNET_TEMPERATURE:
+	case ENVCTRL_RD_SCSI_TEMPERATURE:
+	case ENVCTRL_RD_GLOBALADDRESS:
+		file->private_data = (void *)(long)cmd;
+		break;
+
+	case ENVCTRL_RD_CPU_TEMPERATURE:
+	case ENVCTRL_RD_CPU_VOLTAGE:
+		/* Check to see if application passes in any cpu number,
+		 * the default is cpu0.
+		 */
+		infobuf = (char __user *) arg;
+		if (infobuf == NULL) {
+			read_cpu = 0;
+		}else {
+			get_user(read_cpu, infobuf);
+		}
+
+		/* Save the command for use when reading. */
+		file->private_data = (void *)(long)cmd;
+		break;
+
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+/* Function Description: open device. Mapped to user open().
+ * Return: Always 0.
+ */
+static int
+envctrl_open(struct inode *inode, struct file *file)
+{
+	file->private_data = NULL;
+	return 0;
+}
+
+/* Function Description: Open device. Mapped to user close().
+ * Return: Always 0.
+ */
+static int
+envctrl_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static struct file_operations envctrl_fops = {
+	.owner =	THIS_MODULE,
+	.read =		envctrl_read,
+	.ioctl =	envctrl_ioctl,
+	.open =		envctrl_open,
+	.release =	envctrl_release,
+};	
+
+static struct miscdevice envctrl_dev = {
+	ENVCTRL_MINOR,
+	"envctrl",
+	&envctrl_fops
+};
+
+/* Function Description: Set monitor type based on firmware description.
+ * Return: None.
+ */
+static void envctrl_set_mon(struct i2c_child_t *pchild,
+			    char *chnl_desc,
+			    int chnl_no)
+{
+	/* Firmware only has temperature type.  It does not distinguish
+	 * different kinds of temperatures.  We use channel description
+	 * to disinguish them.
+	 */
+	if (!(strcmp(chnl_desc,"temp,cpu")) ||
+	    !(strcmp(chnl_desc,"temp,cpu0")) ||
+	    !(strcmp(chnl_desc,"temp,cpu1")) ||
+	    !(strcmp(chnl_desc,"temp,cpu2")) ||
+	    !(strcmp(chnl_desc,"temp,cpu3")))
+		pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON;
+
+	if (!(strcmp(chnl_desc,"vddcore,cpu0")) ||
+	    !(strcmp(chnl_desc,"vddcore,cpu1")) ||
+	    !(strcmp(chnl_desc,"vddcore,cpu2")) ||
+	    !(strcmp(chnl_desc,"vddcore,cpu3")))
+		pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON;
+
+	if (!(strcmp(chnl_desc,"temp,motherboard")))
+		pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON;
+
+	if (!(strcmp(chnl_desc,"temp,scsi")))
+		pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON;
+
+	if (!(strcmp(chnl_desc,"temp,ethernet")))
+		pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON;
+}
+
+/* Function Description: Initialize monitor channel with channel desc,
+ *                       decoding tables, monitor type, optional properties.
+ * Return: None.
+ */
+static void envctrl_init_adc(struct i2c_child_t *pchild, int node)
+{
+	char chnls_desc[CHANNEL_DESC_SZ];
+	int i = 0, len;
+	char *pos = chnls_desc;
+
+	/* Firmware describe channels into a stream separated by a '\0'. */
+	len = prom_getproperty(node, "channels-description", chnls_desc,
+			       CHANNEL_DESC_SZ);
+	chnls_desc[CHANNEL_DESC_SZ - 1] = '\0';
+
+	while (len > 0) {
+		int l = strlen(pos) + 1;
+		envctrl_set_mon(pchild, pos, i++);
+		len -= l;
+		pos += l;
+	}
+
+	/* Get optional properties. */
+        len = prom_getproperty(node, "warning-temp", (char *)&warning_temperature,
+			       sizeof(warning_temperature));
+        len = prom_getproperty(node, "shutdown-temp", (char *)&shutdown_temperature,
+			       sizeof(shutdown_temperature));
+}
+
+/* Function Description: Initialize child device monitoring fan status.
+ * Return: None.
+ */
+static void envctrl_init_fanstat(struct i2c_child_t *pchild)
+{
+	int i;
+
+	/* Go through all channels and set up the mask. */
+	for (i = 0; i < pchild->total_chnls; i++)
+		pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+	/* We only need to know if this child has fan status monitored.
+	 * We don't care which channels since we have the mask already.
+	 */
+	pchild->mon_type[0] = ENVCTRL_FANSTAT_MON;
+}
+
+/* Function Description: Initialize child device for global addressing line.
+ * Return: None.
+ */
+static void envctrl_init_globaladdr(struct i2c_child_t *pchild)
+{
+	int i;
+
+	/* Voltage/PowerSupply monitoring is piggybacked 
+	 * with Global Address on CompactPCI.  See comments
+	 * within envctrl_i2c_globaladdr for bit assignments.
+	 *
+	 * The mask is created here by assigning mask bits to each
+	 * bit position that represents PCF8584_VOLTAGE_TYPE data.
+	 * Channel numbers are not consecutive within the globaladdr
+	 * node (why?), so we use the actual counter value as chnls_mask
+	 * index instead of the chnl_array[x].chnl_no value.
+	 *
+	 * NOTE: This loop could be replaced with a constant representing
+	 * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK).
+	 */
+	for (i = 0; i < pchild->total_chnls; i++) {
+		if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) {
+			pchild->voltage_mask |= chnls_mask[i];
+		}
+	}
+
+	/* We only need to know if this child has global addressing 
+	 * line monitored.  We don't care which channels since we know 
+	 * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK).
+	 */
+	pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON;
+}
+
+/* Initialize child device monitoring voltage status. */
+static void envctrl_init_voltage_status(struct i2c_child_t *pchild)
+{
+	int i;
+
+	/* Go through all channels and set up the mask. */
+	for (i = 0; i < pchild->total_chnls; i++)
+		pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no];
+
+	/* We only need to know if this child has voltage status monitored.
+	 * We don't care which channels since we have the mask already.
+	 */
+	pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON;
+}
+
+/* Function Description: Initialize i2c child device.
+ * Return: None.
+ */
+static void envctrl_init_i2c_child(struct linux_ebus_child *edev_child,
+				   struct i2c_child_t *pchild)
+{
+	int node, len, i, tbls_size = 0;
+
+	node = edev_child->prom_node;
+
+	/* Get device address. */
+	len = prom_getproperty(node, "reg",
+			       (char *) &(pchild->addr),
+			       sizeof(pchild->addr));
+
+	/* Get tables property.  Read firmware temperature tables. */
+	len = prom_getproperty(node, "translation",
+			       (char *) pchild->tblprop_array,
+			       (PCF8584_MAX_CHANNELS *
+				sizeof(struct pcf8584_tblprop)));
+	if (len > 0) {
+                pchild->total_tbls = len / sizeof(struct pcf8584_tblprop);
+		for (i = 0; i < pchild->total_tbls; i++) {
+			if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) {
+				tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset;
+			}
+		}
+
+                pchild->tables = kmalloc(tbls_size, GFP_KERNEL);
+		if (pchild->tables == NULL){
+			printk("envctrl: Failed to allocate table.\n");
+			return;
+		}
+                len = prom_getproperty(node, "tables",
+				       (char *) pchild->tables, tbls_size);
+                if (len <= 0) {
+			printk("envctrl: Failed to get table.\n");
+			return;
+		}
+	}
+
+	/* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04)
+	 * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is
+	 * "For Factory Use Only."
+	 *
+	 * We ignore the node on these platforms by assigning the
+	 * 'NULL' monitor type.
+	 */
+	if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) {
+		int len;
+		char prop[56];
+
+		len = prom_getproperty(prom_root_node, "name", prop, sizeof(prop));
+		if (0 < len && (0 == strncmp(prop, "SUNW,UltraSPARC-IIi-cEngine", len)))
+		{
+			for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) {
+				pchild->mon_type[len] = ENVCTRL_NOMON;
+			}
+			return;
+		}
+	}
+
+	/* Get the monitor channels. */
+	len = prom_getproperty(node, "channels-in-use",
+			       (char *) pchild->chnl_array,
+			       (PCF8584_MAX_CHANNELS *
+				sizeof(struct pcf8584_channel)));
+	pchild->total_chnls = len / sizeof(struct pcf8584_channel);
+
+	for (i = 0; i < pchild->total_chnls; i++) {
+		switch (pchild->chnl_array[i].type) {
+		case PCF8584_TEMP_TYPE:
+			envctrl_init_adc(pchild, node);
+			break;
+
+		case PCF8584_GLOBALADDR_TYPE:
+			envctrl_init_globaladdr(pchild);
+			i = pchild->total_chnls;
+			break;
+
+		case PCF8584_FANSTAT_TYPE:
+			envctrl_init_fanstat(pchild);
+			i = pchild->total_chnls;
+			break;
+
+		case PCF8584_VOLTAGE_TYPE:
+			if (pchild->i2ctype == I2C_ADC) {
+				envctrl_init_adc(pchild,node);
+			} else {
+				envctrl_init_voltage_status(pchild);
+			}
+			i = pchild->total_chnls;
+			break;
+
+		default:
+			break;
+		};
+	}
+}
+
+/* Function Description: Search the child device list for a device.
+ * Return : The i2c child if found. NULL otherwise.
+ */
+static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type)
+{
+	int i, j;
+
+	for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) {
+		for (j = 0; j < PCF8584_MAX_CHANNELS; j++) {
+			if (i2c_childlist[i].mon_type[j] == mon_type) {
+				return (struct i2c_child_t *)(&(i2c_childlist[i]));
+			}
+		}
+	}
+	return NULL;
+}
+
+static void envctrl_do_shutdown(void)
+{
+	static int inprog = 0;
+	static char *envp[] = {	
+		"HOME=/", "TERM=linux", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+	char *argv[] = { 
+		"/sbin/shutdown", "-h", "now", NULL };	
+
+	if (inprog != 0)
+		return;
+
+	inprog = 1;
+	printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n");
+	if (0 > execve("/sbin/shutdown", argv, envp)) {
+		printk(KERN_CRIT "kenvctrld: WARNING: system shutdown failed!\n"); 
+		inprog = 0;  /* unlikely to succeed, but we could try again */
+	}
+}
+
+static struct task_struct *kenvctrld_task;
+
+static int kenvctrld(void *__unused)
+{
+	int poll_interval;
+	int whichcpu;
+	char tempbuf[10];
+	struct i2c_child_t *cputemp;
+
+	if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) {
+		printk(KERN_ERR 
+		       "envctrl: kenvctrld unable to monitor CPU temp-- exiting\n");
+		return -ENODEV;
+	}
+
+	poll_interval = 5 * HZ; /* TODO env_mon_interval */
+
+	daemonize("kenvctrld");
+	allow_signal(SIGKILL);
+
+	kenvctrld_task = current;
+
+	printk(KERN_INFO "envctrl: %s starting...\n", current->comm);
+	for (;;) {
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(poll_interval);
+
+		if(signal_pending(current))
+			break;
+
+		for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) {
+			if (0 < envctrl_read_cpu_info(whichcpu, cputemp,
+						      ENVCTRL_CPUTEMP_MON,
+						      tempbuf)) {
+				if (tempbuf[0] >= shutdown_temperature) {
+					printk(KERN_CRIT 
+						"%s: WARNING: CPU%i temperature %i C meets or exceeds "\
+						"shutdown threshold %i C\n", 
+						current->comm, whichcpu, 
+						tempbuf[0], shutdown_temperature);
+					envctrl_do_shutdown();
+				}
+			}
+		}
+	}
+	printk(KERN_INFO "envctrl: %s exiting...\n", current->comm);
+	return 0;
+}
+
+static int __init envctrl_init(void)
+{
+#ifdef CONFIG_PCI
+	struct linux_ebus *ebus = NULL;
+	struct linux_ebus_device *edev = NULL;
+	struct linux_ebus_child *edev_child = NULL;
+	int err, i = 0;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, "bbc")) {
+				/* If we find a boot-bus controller node,
+				 * then this envctrl driver is not for us.
+				 */
+				return -ENODEV;
+			}
+		}
+	}
+
+	/* Traverse through ebus and ebus device list for i2c device and
+	 * adc and gpio nodes.
+	 */
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, "i2c")) {
+				i2c = ioremap(edev->resource[0].start, 0x2);
+				for_each_edevchild(edev, edev_child) {
+					if (!strcmp("gpio", edev_child->prom_name)) {
+						i2c_childlist[i].i2ctype = I2C_GPIO;
+						envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+					}
+					if (!strcmp("adc", edev_child->prom_name)) {
+						i2c_childlist[i].i2ctype = I2C_ADC;
+						envctrl_init_i2c_child(edev_child, &(i2c_childlist[i++]));
+					}
+				}
+				goto done;
+			}
+		}
+	}
+
+done:
+	if (!edev) {
+		printk("envctrl: I2C device not found.\n");
+		return -ENODEV;
+	}
+
+	/* Set device address. */
+	writeb(CONTROL_PIN, i2c + PCF8584_CSR);
+	writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA);
+
+	/* Set system clock and SCL frequencies. */ 
+	writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR);
+	writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA);
+
+	/* Enable serial interface. */
+	writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR);
+	udelay(200);
+
+	/* Register the device as a minor miscellaneous device. */
+	err = misc_register(&envctrl_dev);
+	if (err) {
+		printk("envctrl: Unable to get misc minor %d\n",
+		       envctrl_dev.minor);
+		goto out_iounmap;
+	}
+
+	/* Note above traversal routine post-incremented 'i' to accommodate 
+	 * a next child device, so we decrement before reverse-traversal of
+	 * child devices.
+	 */
+	printk("envctrl: initialized ");
+	for (--i; i >= 0; --i) {
+		printk("[%s 0x%lx]%s", 
+			(I2C_ADC == i2c_childlist[i].i2ctype) ? ("adc") : 
+			((I2C_GPIO == i2c_childlist[i].i2ctype) ? ("gpio") : ("unknown")), 
+			i2c_childlist[i].addr, (0 == i) ? ("\n") : (" "));
+	}
+
+	err = kernel_thread(kenvctrld, NULL, CLONE_FS | CLONE_FILES);
+	if (err < 0)
+		goto out_deregister;
+
+	return 0;
+
+out_deregister:
+	misc_deregister(&envctrl_dev);
+out_iounmap:
+	iounmap(i2c);
+	for (i = 0; i < ENVCTRL_MAX_CPU * 2; i++) {
+		if (i2c_childlist[i].tables)
+			kfree(i2c_childlist[i].tables);
+	}
+	return err;
+#else
+	return -ENODEV;
+#endif
+}
+
+static void __exit envctrl_cleanup(void)
+{
+	int i;
+
+	if (NULL != kenvctrld_task) {
+		force_sig(SIGKILL, kenvctrld_task);
+		for (;;) {
+			struct task_struct *p;
+			int found = 0;
+
+			read_lock(&tasklist_lock);
+			for_each_process(p) {
+				if (p == kenvctrld_task) {
+					found = 1;
+					break;
+				}
+			}
+			read_unlock(&tasklist_lock);
+
+			if (!found)
+				break;
+
+			msleep(1000);
+		}
+		kenvctrld_task = NULL;
+	}
+
+	iounmap(i2c);
+	misc_deregister(&envctrl_dev);
+
+	for (i = 0; i < ENVCTRL_MAX_CPU * 2; i++) {
+		if (i2c_childlist[i].tables)
+			kfree(i2c_childlist[i].tables);
+	}
+}
+
+module_init(envctrl_init);
+module_exit(envctrl_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c
new file mode 100644
index 0000000..6bdd768
--- /dev/null
+++ b/drivers/sbus/char/flash.c
@@ -0,0 +1,255 @@
+/* $Id: flash.c,v 1.25 2001/12/21 04:56:16 davem Exp $
+ * flash.c: Allow mmap access to the OBP Flash, for OBP updates.
+ *
+ * Copyright (C) 1997  Eddie C. Dost  (ecd@skynet.be)
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <linux/spinlock.h>
+
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/sbus.h>
+#include <asm/ebus.h>
+#include <asm/upa.h>
+
+static DEFINE_SPINLOCK(flash_lock);
+static struct {
+	unsigned long read_base;	/* Physical read address */
+	unsigned long write_base;	/* Physical write address */
+	unsigned long read_size;	/* Size of read area */
+	unsigned long write_size;	/* Size of write area */
+	unsigned long busy;		/* In use? */
+} flash;
+
+#define FLASH_MINOR	152
+
+static int
+flash_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	unsigned long addr;
+	unsigned long size;
+
+	spin_lock(&flash_lock);
+	if (flash.read_base == flash.write_base) {
+		addr = flash.read_base;
+		size = flash.read_size;
+	} else {
+		if ((vma->vm_flags & VM_READ) &&
+		    (vma->vm_flags & VM_WRITE)) {
+			spin_unlock(&flash_lock);
+			return -EINVAL;
+		}
+		if (vma->vm_flags & VM_READ) {
+			addr = flash.read_base;
+			size = flash.read_size;
+		} else if (vma->vm_flags & VM_WRITE) {
+			addr = flash.write_base;
+			size = flash.write_size;
+		} else {
+			spin_unlock(&flash_lock);
+			return -ENXIO;
+		}
+	}
+	spin_unlock(&flash_lock);
+
+	if ((vma->vm_pgoff << PAGE_SHIFT) > size)
+		return -ENXIO;
+	addr = vma->vm_pgoff + (addr >> PAGE_SHIFT);
+
+	if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size)
+		size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT));
+
+	pgprot_val(vma->vm_page_prot) &= ~(_PAGE_CACHE);
+	pgprot_val(vma->vm_page_prot) |= _PAGE_E;
+	vma->vm_flags |= (VM_SHM | VM_LOCKED);
+
+	if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot))
+		return -EAGAIN;
+		
+	return 0;
+}
+
+static long long
+flash_llseek(struct file *file, long long offset, int origin)
+{
+	lock_kernel();
+	switch (origin) {
+		case 0:
+			file->f_pos = offset;
+			break;
+		case 1:
+			file->f_pos += offset;
+			if (file->f_pos > flash.read_size)
+				file->f_pos = flash.read_size;
+			break;
+		case 2:
+			file->f_pos = flash.read_size;
+			break;
+		default:
+			unlock_kernel();
+			return -EINVAL;
+	}
+	unlock_kernel();
+	return file->f_pos;
+}
+
+static ssize_t
+flash_read(struct file * file, char __user * buf,
+	   size_t count, loff_t *ppos)
+{
+	unsigned long p = file->f_pos;
+	int i;
+	
+	if (count > flash.read_size - p)
+		count = flash.read_size - p;
+
+	for (i = 0; i < count; i++) {
+		u8 data = upa_readb(flash.read_base + p + i);
+		if (put_user(data, buf))
+			return -EFAULT;
+		buf++;
+	}
+
+	file->f_pos += count;
+	return count;
+}
+
+static int
+flash_open(struct inode *inode, struct file *file)
+{
+	if (test_and_set_bit(0, (void *)&flash.busy) != 0)
+		return -EBUSY;
+
+	return 0;
+}
+
+static int
+flash_release(struct inode *inode, struct file *file)
+{
+	spin_lock(&flash_lock);
+	flash.busy = 0;
+	spin_unlock(&flash_lock);
+
+	return 0;
+}
+
+static struct file_operations flash_fops = {
+	/* no write to the Flash, use mmap
+	 * and play flash dependent tricks.
+	 */
+	.owner =	THIS_MODULE,
+	.llseek =	flash_llseek,
+	.read =		flash_read,
+	.mmap =		flash_mmap,
+	.open =		flash_open,
+	.release =	flash_release,
+};
+
+static struct miscdevice flash_dev = { FLASH_MINOR, "flash", &flash_fops };
+
+static int __init flash_init(void)
+{
+	struct sbus_bus *sbus;
+	struct sbus_dev *sdev = NULL;
+#ifdef CONFIG_PCI
+	struct linux_ebus *ebus;
+	struct linux_ebus_device *edev = NULL;
+	struct linux_prom_registers regs[2];
+	int len, nregs;
+#endif
+	int err;
+
+	for_all_sbusdev(sdev, sbus) {
+		if (!strcmp(sdev->prom_name, "flashprom")) {
+			if (sdev->reg_addrs[0].phys_addr == sdev->reg_addrs[1].phys_addr) {
+				flash.read_base = ((unsigned long)sdev->reg_addrs[0].phys_addr) |
+					(((unsigned long)sdev->reg_addrs[0].which_io)<<32UL);
+				flash.read_size = sdev->reg_addrs[0].reg_size;
+				flash.write_base = flash.read_base;
+				flash.write_size = flash.read_size;
+			} else {
+				flash.read_base = ((unsigned long)sdev->reg_addrs[0].phys_addr) |
+					(((unsigned long)sdev->reg_addrs[0].which_io)<<32UL);
+				flash.read_size = sdev->reg_addrs[0].reg_size;
+				flash.write_base = ((unsigned long)sdev->reg_addrs[1].phys_addr) |
+					(((unsigned long)sdev->reg_addrs[1].which_io)<<32UL);
+				flash.write_size = sdev->reg_addrs[1].reg_size;
+			}
+			flash.busy = 0;
+			break;
+		}
+	}
+	if (!sdev) {
+#ifdef CONFIG_PCI
+		for_each_ebus(ebus) {
+			for_each_ebusdev(edev, ebus) {
+				if (!strcmp(edev->prom_name, "flashprom"))
+					goto ebus_done;
+			}
+		}
+	ebus_done:
+		if (!edev)
+			return -ENODEV;
+
+		len = prom_getproperty(edev->prom_node, "reg", (void *)regs, sizeof(regs));
+		if ((len % sizeof(regs[0])) != 0) {
+			printk("flash: Strange reg property size %d\n", len);
+			return -ENODEV;
+		}
+
+		nregs = len / sizeof(regs[0]);
+
+		flash.read_base = edev->resource[0].start;
+		flash.read_size = regs[0].reg_size;
+
+		if (nregs == 1) {
+			flash.write_base = edev->resource[0].start;
+			flash.write_size = regs[0].reg_size;
+		} else if (nregs == 2) {
+			flash.write_base = edev->resource[1].start;
+			flash.write_size = regs[1].reg_size;
+		} else {
+			printk("flash: Strange number of regs %d\n", nregs);
+			return -ENODEV;
+		}
+
+		flash.busy = 0;
+
+#else
+		return -ENODEV;
+#endif
+	}
+
+	printk("OBP Flash: RD %lx[%lx] WR %lx[%lx]\n",
+	       flash.read_base, flash.read_size,
+	       flash.write_base, flash.write_size);
+
+	err = misc_register(&flash_dev);
+	if (err) {
+		printk(KERN_ERR "flash: unable to get misc minor\n");
+		return err;
+	}
+
+	return 0;
+}
+
+static void __exit flash_cleanup(void)
+{
+	misc_deregister(&flash_dev);
+}
+
+module_init(flash_init);
+module_exit(flash_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/jsflash.c b/drivers/sbus/char/jsflash.c
new file mode 100644
index 0000000..c12c504
--- /dev/null
+++ b/drivers/sbus/char/jsflash.c
@@ -0,0 +1,627 @@
+/*
+ * drivers/sbus/char/jsflash.c
+ *
+ *  Copyright (C) 1991, 1992  Linus Torvalds	(drivers/char/mem.c)
+ *  Copyright (C) 1997  Eddie C. Dost		(drivers/sbus/char/flash.c)
+ *  Copyright (C) 1997-2000 Pavel Machek <pavel@ucw.cz>   (drivers/block/nbd.c)
+ *  Copyright (C) 1999-2000 Pete Zaitcev
+ *
+ * This driver is used to program OS into a Flash SIMM on
+ * Krups and Espresso platforms.
+ *
+ * TODO: do not allow erase/programming if file systems are mounted.
+ * TODO: Erase/program both banks of a 8MB SIMM.
+ *
+ * It is anticipated that programming an OS Flash will be a routine
+ * procedure. In the same time it is exeedingly dangerous because
+ * a user can program its OBP flash with OS image and effectively
+ * kill the machine.
+ *
+ * This driver uses an interface different from Eddie's flash.c
+ * as a silly safeguard.
+ *
+ * XXX The flash.c manipulates page caching characteristics in a certain
+ * dubious way; also it assumes that remap_pfn_range() can remap
+ * PCI bus locations, which may be false. ioremap() must be used
+ * instead. We should discuss this.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/smp_lock.h>
+#include <linux/genhd.h>
+#include <linux/blkdev.h>
+
+#define MAJOR_NR	JSFD_MAJOR
+
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/pcic.h>
+#include <asm/oplib.h>
+
+#include <asm/jsflash.h>		/* ioctl arguments. <linux/> ?? */
+#define JSFIDSZ		(sizeof(struct jsflash_ident_arg))
+#define JSFPRGSZ	(sizeof(struct jsflash_program_arg))
+
+/*
+ * Our device numbers have no business in system headers.
+ * The only thing a user knows is the device name /dev/jsflash.
+ *
+ * Block devices are laid out like this:
+ *   minor+0	- Bootstrap, for 8MB SIMM 0x20400000[0x800000]
+ *   minor+1	- Filesystem to mount, normally 0x20400400[0x7ffc00]
+ *   minor+2	- Whole flash area for any case... 0x20000000[0x01000000]
+ * Total 3 minors per flash device.
+ *
+ * It is easier to have static size vectors, so we define
+ * a total minor range JSF_MAX, which must cover all minors.
+ */
+/* character device */
+#define JSF_MINOR	178	/* 178 is registered with hpa */
+/* block device */
+#define JSF_MAX		 3	/* 3 minors wasted total so far. */
+#define JSF_NPART	 3	/* 3 minors per flash device */
+#define JSF_PART_BITS	 2	/* 2 bits of minors to cover JSF_NPART */
+#define JSF_PART_MASK	 0x3	/* 2 bits mask */
+
+/*
+ * Access functions.
+ * We could ioremap(), but it's easier this way.
+ */
+static unsigned int jsf_inl(unsigned long addr)
+{
+	unsigned long retval;
+
+	__asm__ __volatile__("lda [%1] %2, %0\n\t" :
+				"=r" (retval) :
+				"r" (addr), "i" (ASI_M_BYPASS));
+        return retval;
+}
+
+static void jsf_outl(unsigned long addr, __u32 data)
+{
+
+	__asm__ __volatile__("sta %0, [%1] %2\n\t" : :
+				"r" (data), "r" (addr), "i" (ASI_M_BYPASS) :
+				"memory");
+}
+
+/*
+ * soft carrier
+ */
+
+struct jsfd_part {
+	unsigned long dbase;
+	unsigned long dsize;
+};
+
+struct jsflash {
+	unsigned long base;
+	unsigned long size;
+	unsigned long busy;		/* In use? */
+	struct jsflash_ident_arg id;
+	/* int mbase; */		/* Minor base, typically zero */
+	struct jsfd_part dv[JSF_NPART];
+};
+
+/*
+ * We do not map normal memory or obio as a safety precaution.
+ * But offsets are real, for ease of userland programming.
+ */
+#define JSF_BASE_TOP	0x30000000
+#define JSF_BASE_ALL	0x20000000
+
+#define JSF_BASE_JK	0x20400000
+
+/*
+ */
+static struct gendisk *jsfd_disk[JSF_MAX];
+
+/*
+ * Let's pretend we may have several of these...
+ */
+static struct jsflash jsf0;
+
+/*
+ * Wait for AMD to finish its embedded algorithm.
+ * We use the Toggle bit DQ6 (0x40) because it does not
+ * depend on the data value as /DATA bit DQ7 does.
+ *
+ * XXX Do we need any timeout here? So far it never hanged, beware broken hw.
+ */
+static void jsf_wait(unsigned long p) {
+	unsigned int x1, x2;
+
+	for (;;) {
+		x1 = jsf_inl(p);
+		x2 = jsf_inl(p);
+		if ((x1 & 0x40404040) == (x2 & 0x40404040)) return;
+	}
+}
+
+/*
+ * Programming will only work if Flash is clean,
+ * we leave it to the programmer application.
+ *
+ * AMD must be programmed one byte at a time;
+ * thus, Simple Tech SIMM must be written 4 bytes at a time.
+ *
+ * Write waits for the chip to become ready after the write
+ * was finished. This is done so that application would read
+ * consistent data after the write is done.
+ */
+static void jsf_write4(unsigned long fa, u32 data) {
+
+	jsf_outl(fa, 0xAAAAAAAA);		/* Unlock 1 Write 1 */
+	jsf_outl(fa, 0x55555555);		/* Unlock 1 Write 2 */
+	jsf_outl(fa, 0xA0A0A0A0);		/* Byte Program */
+	jsf_outl(fa, data);
+
+	jsf_wait(fa);
+}
+
+/*
+ */
+static void jsfd_read(char *buf, unsigned long p, size_t togo) {
+	union byte4 {
+		char s[4];
+		unsigned int n;
+	} b;
+
+	while (togo >= 4) {
+		togo -= 4;
+		b.n = jsf_inl(p);
+		memcpy(buf, b.s, 4);
+		p += 4;
+		buf += 4;
+	}
+}
+
+static void jsfd_do_request(request_queue_t *q)
+{
+	struct request *req;
+
+	while ((req = elv_next_request(q)) != NULL) {
+		struct jsfd_part *jdp = req->rq_disk->private_data;
+		unsigned long offset = req->sector << 9;
+		size_t len = req->current_nr_sectors << 9;
+
+		if ((offset + len) > jdp->dsize) {
+               		end_request(req, 0);
+			continue;
+		}
+
+		if (rq_data_dir(req) != READ) {
+			printk(KERN_ERR "jsfd: write\n");
+			end_request(req, 0);
+			continue;
+		}
+
+		if ((jdp->dbase & 0xff000000) != 0x20000000) {
+			printk(KERN_ERR "jsfd: bad base %x\n", (int)jdp->dbase);
+			end_request(req, 0);
+			continue;
+		}
+
+		jsfd_read(req->buffer, jdp->dbase + offset, len);
+
+		end_request(req, 1);
+	}
+}
+
+/*
+ * 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 jsf_lseek(struct file * file, loff_t offset, int orig)
+{
+	loff_t ret;
+
+	lock_kernel();
+	switch (orig) {
+		case 0:
+			file->f_pos = offset;
+			ret = file->f_pos;
+			break;
+		case 1:
+			file->f_pos += offset;
+			ret = file->f_pos;
+			break;
+		default:
+			ret = -EINVAL;
+	}
+	unlock_kernel();
+	return ret;
+}
+
+/*
+ * OS SIMM Cannot be read in other size but a 32bits word.
+ */
+static ssize_t jsf_read(struct file * file, char * buf, 
+    size_t togo, loff_t *ppos)
+{
+	unsigned long p = *ppos;
+	char *tmp = buf;
+
+	union byte4 {
+		char s[4];
+		unsigned int n;
+	} b;
+
+	if (p < JSF_BASE_ALL || p >= JSF_BASE_TOP) {
+		return 0;
+	}
+
+	if ((p + togo) < p	/* wrap */
+	   || (p + togo) >= JSF_BASE_TOP) {
+		togo = JSF_BASE_TOP - p;
+	}
+
+	if (p < JSF_BASE_ALL && togo != 0) {
+#if 0 /* __bzero XXX */
+		size_t x = JSF_BASE_ALL - p;
+		if (x > togo) x = togo;
+		clear_user(tmp, x);
+		tmp += x;
+		p += x;
+		togo -= x;
+#else
+		/*
+		 * Implementation of clear_user() calls __bzero
+		 * without regard to modversions,
+		 * so we cannot build a module.
+		 */
+		return 0;
+#endif
+	}
+
+	while (togo >= 4) {
+		togo -= 4;
+		b.n = jsf_inl(p);
+		if (copy_to_user(tmp, b.s, 4))
+			return -EFAULT;
+		tmp += 4;
+		p += 4;
+	}
+
+	/*
+	 * XXX Small togo may remain if 1 byte is ordered.
+	 * It would be nice if we did a word size read and unpacked it.
+	 */
+
+	*ppos = p;
+	return tmp-buf;
+}
+
+static ssize_t jsf_write(struct file * file, const char * buf,
+    size_t count, loff_t *ppos)
+{
+	return -ENOSPC;
+}
+
+/*
+ */
+static int jsf_ioctl_erase(unsigned long arg)
+{
+	unsigned long p;
+
+	/* p = jsf0.base;	hits wrong bank */
+	p = 0x20400000;
+
+	jsf_outl(p, 0xAAAAAAAA);		/* Unlock 1 Write 1 */
+	jsf_outl(p, 0x55555555);		/* Unlock 1 Write 2 */
+	jsf_outl(p, 0x80808080);		/* Erase setup */
+	jsf_outl(p, 0xAAAAAAAA);		/* Unlock 2 Write 1 */
+	jsf_outl(p, 0x55555555);		/* Unlock 2 Write 2 */
+	jsf_outl(p, 0x10101010);		/* Chip erase */
+
+#if 0
+	/*
+	 * This code is ok, except that counter based timeout
+	 * has no place in this world. Let's just drop timeouts...
+	 */
+	{
+		int i;
+		__u32 x;
+		for (i = 0; i < 1000000; i++) {
+			x = jsf_inl(p);
+			if ((x & 0x80808080) == 0x80808080) break;
+		}
+		if ((x & 0x80808080) != 0x80808080) {
+			printk("jsf0: erase timeout with 0x%08x\n", x);
+		} else {
+			printk("jsf0: erase done with 0x%08x\n", x);
+		}
+	}
+#else
+	jsf_wait(p);
+#endif
+
+	return 0;
+}
+
+/*
+ * Program a block of flash.
+ * Very simple because we can do it byte by byte anyway.
+ */
+static int jsf_ioctl_program(unsigned long arg)
+{
+	struct jsflash_program_arg abuf;
+	char *uptr;
+	unsigned long p;
+	unsigned int togo;
+	union {
+		unsigned int n;
+		char s[4];
+	} b;
+
+	if (copy_from_user(&abuf, (char *)arg, JSFPRGSZ))
+		return -EFAULT; 
+	p = abuf.off;
+	togo = abuf.size;
+	if ((togo & 3) || (p & 3)) return -EINVAL;
+
+	uptr = (char *) (unsigned long) abuf.data;
+	while (togo != 0) {
+		togo -= 4;
+		if (copy_from_user(&b.s[0], uptr, 4))
+			return -EFAULT;
+		jsf_write4(p, b.n);
+		p += 4;
+		uptr += 4;
+	}
+
+	return 0;
+}
+
+static int jsf_ioctl(struct inode *inode, struct file *f, unsigned int cmd,
+    unsigned long arg)
+{
+	int error = -ENOTTY;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+	switch (cmd) {
+	case JSFLASH_IDENT:
+		if (copy_to_user((void *)arg, &jsf0.id, JSFIDSZ))
+			return -EFAULT;
+		break;
+	case JSFLASH_ERASE:
+		error = jsf_ioctl_erase(arg);
+		break;
+	case JSFLASH_PROGRAM:
+		error = jsf_ioctl_program(arg);
+		break;
+	}
+
+	return error;
+}
+
+static int jsf_mmap(struct file * file, struct vm_area_struct * vma)
+{
+	return -ENXIO;
+}
+
+static int jsf_open(struct inode * inode, struct file * filp)
+{
+
+	if (jsf0.base == 0) return -ENXIO;
+	if (test_and_set_bit(0, (void *)&jsf0.busy) != 0)
+		return -EBUSY;
+
+	return 0;	/* XXX What security? */
+}
+
+static int jsf_release(struct inode *inode, struct file *file)
+{
+	jsf0.busy = 0;
+	return 0;
+}
+
+static struct file_operations jsf_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	jsf_lseek,
+	.read =		jsf_read,
+	.write =	jsf_write,
+	.ioctl =	jsf_ioctl,
+	.mmap =		jsf_mmap,
+	.open =		jsf_open,
+	.release =	jsf_release,
+};
+
+static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
+
+static struct block_device_operations jsfd_fops = {
+	.owner =	THIS_MODULE,
+};
+
+static int jsflash_init(void)
+{
+	int rc;
+	struct jsflash *jsf;
+	int node;
+	char banner[128];
+	struct linux_prom_registers reg0;
+
+	node = prom_getchild(prom_root_node);
+	node = prom_searchsiblings(node, "flash-memory");
+	if (node != 0 && node != -1) {
+		if (prom_getproperty(node, "reg",
+		    (char *)&reg0, sizeof(reg0)) == -1) {
+			printk("jsflash: no \"reg\" property\n");
+			return -ENXIO;
+		}
+		if (reg0.which_io != 0) {
+			printk("jsflash: bus number nonzero: 0x%x:%x\n",
+			    reg0.which_io, reg0.phys_addr);
+			return -ENXIO;
+		}
+		/*
+		 * Flash may be somewhere else, for instance on Ebus.
+		 * So, don't do the following check for IIep flash space.
+		 */
+#if 0
+		if ((reg0.phys_addr >> 24) != 0x20) {
+			printk("jsflash: suspicious address: 0x%x:%x\n",
+			    reg0.which_io, reg0.phys_addr);
+			return -ENXIO;
+		}
+#endif
+		if ((int)reg0.reg_size <= 0) {
+			printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
+			return -ENXIO;
+		}
+	} else {
+		/* XXX Remove this code once PROLL ID12 got widespread */
+		printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
+		prom_getproperty(prom_root_node, "banner-name", banner, 128);
+		if (strcmp (banner, "JavaStation-NC") != 0 &&
+		    strcmp (banner, "JavaStation-E") != 0) {
+			return -ENXIO;
+		}
+		reg0.which_io = 0;
+		reg0.phys_addr = 0x20400000;
+		reg0.reg_size  = 0x00800000;
+	}
+
+	/* Let us be really paranoid for modifications to probing code. */
+	/* extern enum sparc_cpu sparc_cpu_model; */ /* in <asm/system.h> */
+	if (sparc_cpu_model != sun4m) {
+		/* We must be on sun4m because we use MMU Bypass ASI. */
+		return -ENXIO;
+	}
+
+	if (jsf0.base == 0) {
+		jsf = &jsf0;
+
+		jsf->base = reg0.phys_addr;
+		jsf->size = reg0.reg_size;
+
+		/* XXX Redo the userland interface. */
+		jsf->id.off = JSF_BASE_ALL;
+		jsf->id.size = 0x01000000;	/* 16M - all segments */
+		strcpy(jsf->id.name, "Krups_all");
+
+		jsf->dv[0].dbase = jsf->base;
+		jsf->dv[0].dsize = jsf->size;
+		jsf->dv[1].dbase = jsf->base + 1024;
+		jsf->dv[1].dsize = jsf->size - 1024;
+		jsf->dv[2].dbase = JSF_BASE_ALL;
+		jsf->dv[2].dsize = 0x01000000;
+
+		printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
+		    (int) (jsf->size / (1024*1024)));
+	}
+
+	if ((rc = misc_register(&jsf_dev)) != 0) {
+		printk(KERN_ERR "jsf: unable to get misc minor %d\n",
+		    JSF_MINOR);
+		jsf0.base = 0;
+		return rc;
+	}
+
+	return 0;
+}
+
+static struct request_queue *jsf_queue;
+
+static int jsfd_init(void)
+{
+	static DEFINE_SPINLOCK(lock);
+	struct jsflash *jsf;
+	struct jsfd_part *jdp;
+	int err;
+	int i;
+
+	if (jsf0.base == 0)
+		return -ENXIO;
+
+	err = -ENOMEM;
+	for (i = 0; i < JSF_MAX; i++) {
+		struct gendisk *disk = alloc_disk(1);
+		if (!disk)
+			goto out;
+		jsfd_disk[i] = disk;
+	}
+
+	if (register_blkdev(JSFD_MAJOR, "jsfd")) {
+		err = -EIO;
+		goto out;
+	}
+
+	jsf_queue = blk_init_queue(jsfd_do_request, &lock);
+	if (!jsf_queue) {
+		err = -ENOMEM;
+		unregister_blkdev(JSFD_MAJOR, "jsfd");
+		goto out;
+	}
+
+	for (i = 0; i < JSF_MAX; i++) {
+		struct gendisk *disk = jsfd_disk[i];
+		if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+		jsf = &jsf0;	/* actually, &jsfv[i >> JSF_PART_BITS] */
+		jdp = &jsf->dv[i&JSF_PART_MASK];
+
+		disk->major = JSFD_MAJOR;
+		disk->first_minor = i;
+		sprintf(disk->disk_name, "jsfd%d", i);
+		disk->fops = &jsfd_fops;
+		set_capacity(disk, jdp->dsize >> 9);
+		disk->private_data = jdp;
+		disk->queue = jsf_queue;
+		add_disk(disk);
+		set_disk_ro(disk, 1);
+	}
+	return 0;
+out:
+	while (i--)
+		put_disk(jsfd_disk[i]);
+	return err;
+}
+
+MODULE_LICENSE("GPL");
+
+static int __init jsflash_init_module(void) {
+	int rc;
+
+	if ((rc = jsflash_init()) == 0) {
+		jsfd_init();
+		return 0;
+	}
+	return rc;
+}
+
+static void __exit jsflash_cleanup_module(void)
+{
+	int i;
+
+	for (i = 0; i < JSF_MAX; i++) {
+		if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+		del_gendisk(jsfd_disk[i]);
+		put_disk(jsfd_disk[i]);
+	}
+	if (jsf0.busy)
+		printk("jsf0: cleaning busy unit\n");
+	jsf0.base = 0;
+	jsf0.busy = 0;
+
+	misc_deregister(&jsf_dev);
+	if (unregister_blkdev(JSFD_MAJOR, "jsfd") != 0)
+		printk("jsfd: cleanup_module failed\n");
+	blk_cleanup_queue(jsf_queue);
+}
+
+module_init(jsflash_init_module);
+module_exit(jsflash_cleanup_module);
diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h
new file mode 100644
index 0000000..0bb09c2
--- /dev/null
+++ b/drivers/sbus/char/max1617.h
@@ -0,0 +1,27 @@
+/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */
+#ifndef _MAX1617_H
+#define _MAX1617_H
+
+#define MAX1617_AMB_TEMP	0x00 /* Ambient temp in C	*/
+#define MAX1617_CPU_TEMP	0x01 /* Processor die temp in C	*/
+#define MAX1617_STATUS		0x02 /* Chip status bits	*/
+
+/* Read-only versions of changable registers. */
+#define MAX1617_RD_CFG_BYTE	0x03 /* Config register		*/
+#define MAX1617_RD_CVRATE_BYTE	0x04 /* Temp conversion rate	*/
+#define MAX1617_RD_AMB_HIGHLIM	0x05 /* Ambient high limit	*/
+#define MAX1617_RD_AMB_LOWLIM	0x06 /* Ambient low limit	*/
+#define MAX1617_RD_CPU_HIGHLIM	0x07 /* Processor high limit	*/
+#define MAX1617_RD_CPU_LOWLIM	0x08 /* Processor low limit	*/
+
+/* Write-only versions of the same. */
+#define MAX1617_WR_CFG_BYTE	0x09
+#define MAX1617_WR_CVRATE_BYTE	0x0a
+#define MAX1617_WR_AMB_HIGHLIM	0x0b
+#define MAX1617_WR_AMB_LOWLIM	0x0c
+#define MAX1617_WR_CPU_HIGHLIM	0x0d
+#define MAX1617_WR_CPU_LOWLIM	0x0e
+
+#define MAX1617_ONESHOT		0x0f
+
+#endif /* _MAX1617_H */
diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c
new file mode 100644
index 0000000..58ed337
--- /dev/null
+++ b/drivers/sbus/char/openprom.c
@@ -0,0 +1,630 @@
+/*
+ * Linux/SPARC PROM Configuration Driver
+ * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu)
+ * Copyright (C) 1996 Eddie C. Dost  (ecd@skynet.be)
+ *
+ * This character device driver allows user programs to access the
+ * PROM device tree. It is compatible with the SunOS /dev/openprom
+ * driver and the NetBSD /dev/openprom driver. The SunOS eeprom
+ * utility works without any modifications.
+ *
+ * The driver uses a minor number under the misc device major. The
+ * file read/write mode determines the type of access to the PROM.
+ * Interrupts are disabled whenever the driver calls into the PROM for
+ * sanity's sake.
+ */
+
+/* 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. 
+ */
+
+#define PROMLIB_INTERNAL
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/openpromio.h>
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#include <asm/pbm.h>
+#endif
+
+/* Private data kept by the driver for each descriptor. */
+typedef struct openprom_private_data
+{
+	int current_node;	/* Current node for SunOS ioctls. */
+	int lastnode;		/* Last valid node used by BSD ioctls. */
+} DATA;
+
+/* ID of the PROM node containing all of the EEPROM options. */
+static int options_node = 0;
+
+/*
+ * Copy an openpromio structure into kernel space from user space.
+ * This routine does error checking to make sure that all memory
+ * accesses are within bounds. A pointer to the allocated openpromio
+ * structure will be placed in "*opp_p". Return value is the length
+ * of the user supplied buffer.
+ */
+static int copyin(struct openpromio __user *info, struct openpromio **opp_p)
+{
+	unsigned int bufsize;
+
+	if (!info || !opp_p)
+		return -EFAULT;
+
+	if (get_user(bufsize, &info->oprom_size))
+		return -EFAULT;
+
+	if (bufsize == 0)
+		return -EINVAL;
+
+	/* If the bufsize is too large, just limit it.
+	 * Fix from Jason Rappleye.
+	 */
+	if (bufsize > OPROMMAXPARAM)
+		bufsize = OPROMMAXPARAM;
+
+	if (!(*opp_p = kmalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
+		return -ENOMEM;
+	memset(*opp_p, 0, sizeof(int) + bufsize + 1);
+
+	if (copy_from_user(&(*opp_p)->oprom_array,
+			   &info->oprom_array, bufsize)) {
+		kfree(*opp_p);
+		return -EFAULT;
+	}
+	return bufsize;
+}
+
+static int getstrings(struct openpromio __user *info, struct openpromio **opp_p)
+{
+	int n, bufsize;
+	char c;
+
+	if (!info || !opp_p)
+		return -EFAULT;
+
+	if (!(*opp_p = kmalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
+		return -ENOMEM;
+
+	memset(*opp_p, 0, sizeof(int) + OPROMMAXPARAM + 1);
+	(*opp_p)->oprom_size = 0;
+
+	n = bufsize = 0;
+	while ((n < 2) && (bufsize < OPROMMAXPARAM)) {
+		if (get_user(c, &info->oprom_array[bufsize])) {
+			kfree(*opp_p);
+			return -EFAULT;
+		}
+		if (c == '\0')
+			n++;
+		(*opp_p)->oprom_array[bufsize++] = c;
+	}
+	if (!n) {
+		kfree(*opp_p);
+		return -EINVAL;
+	}
+	return bufsize;
+}
+
+/*
+ * Copy an openpromio structure in kernel space back to user space.
+ */
+static int copyout(void __user *info, struct openpromio *opp, int len)
+{
+	if (copy_to_user(info, opp, len))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ *	SunOS and Solaris /dev/openprom ioctl calls.
+ */
+static int openprom_sunos_ioctl(struct inode * inode, struct file * file,
+				unsigned int cmd, unsigned long arg, int node)
+{
+	DATA *data = (DATA *) file->private_data;
+	char buffer[OPROMMAXPARAM+1], *buf;
+	struct openpromio *opp;
+	int bufsize, len, error = 0;
+	static int cnt;
+	void __user *argp = (void __user *)arg;
+
+	if (cmd == OPROMSETOPT)
+		bufsize = getstrings(argp, &opp);
+	else
+		bufsize = copyin(argp, &opp);
+
+	if (bufsize < 0)
+		return bufsize;
+
+	switch (cmd) {
+	case OPROMGETOPT:
+	case OPROMGETPROP:
+		len = prom_getproplen(node, opp->oprom_array);
+
+		if (len <= 0 || len > bufsize) {
+			error = copyout(argp, opp, sizeof(int));
+			break;
+		}
+
+		len = prom_getproperty(node, opp->oprom_array, buffer, bufsize);
+
+		memcpy(opp->oprom_array, buffer, len);
+		opp->oprom_array[len] = '\0';
+		opp->oprom_size = len;
+
+		error = copyout(argp, opp, sizeof(int) + bufsize);
+		break;
+
+	case OPROMNXTOPT:
+	case OPROMNXTPROP:
+		buf = prom_nextprop(node, opp->oprom_array, buffer);
+
+		len = strlen(buf);
+		if (len == 0 || len + 1 > bufsize) {
+			error = copyout(argp, opp, sizeof(int));
+			break;
+		}
+
+		memcpy(opp->oprom_array, buf, len);
+		opp->oprom_array[len] = '\0';
+		opp->oprom_size = ++len;
+
+		error = copyout(argp, opp, sizeof(int) + bufsize);
+		break;
+
+	case OPROMSETOPT:
+	case OPROMSETOPT2:
+		buf = opp->oprom_array + strlen(opp->oprom_array) + 1;
+		len = opp->oprom_array + bufsize - buf;
+
+		error = prom_setprop(options_node, opp->oprom_array,
+				     buf, len);
+
+		if (error < 0)
+			error = -EINVAL;
+		break;
+
+	case OPROMNEXT:
+	case OPROMCHILD:
+	case OPROMSETCUR:
+		if (bufsize < sizeof(int)) {
+			error = -EINVAL;
+			break;
+		}
+
+		node = *((int *) opp->oprom_array);
+
+		switch (cmd) {
+		case OPROMNEXT: node = __prom_getsibling(node); break;
+		case OPROMCHILD: node = __prom_getchild(node); break;
+		case OPROMSETCUR: break;
+		}
+
+		data->current_node = node;
+		*((int *)opp->oprom_array) = node;
+		opp->oprom_size = sizeof(int);
+
+		error = copyout(argp, opp, bufsize + sizeof(int));
+		break;
+
+	case OPROMPCI2NODE:
+		error = -EINVAL;
+
+		if (bufsize >= 2*sizeof(int)) {
+#ifdef CONFIG_PCI
+			struct pci_dev *pdev;
+			struct pcidev_cookie *pcp;
+			pdev = pci_find_slot (((int *) opp->oprom_array)[0],
+					      ((int *) opp->oprom_array)[1]);
+
+			pcp = pdev->sysdata;
+			if (pcp != NULL && pcp->prom_node != -1 && pcp->prom_node) {
+				node = pcp->prom_node;
+				data->current_node = node;
+				*((int *)opp->oprom_array) = node;
+				opp->oprom_size = sizeof(int);
+				error = copyout(argp, opp, bufsize + sizeof(int));
+			}
+#endif
+		}
+		break;
+
+	case OPROMPATH2NODE:
+		node = prom_finddevice(opp->oprom_array);
+		data->current_node = node;
+		*((int *)opp->oprom_array) = node;
+		opp->oprom_size = sizeof(int);
+
+		error = copyout(argp, opp, bufsize + sizeof(int));
+		break;
+
+	case OPROMGETBOOTARGS:
+		buf = saved_command_line;
+
+		len = strlen(buf);
+
+		if (len > bufsize) {
+			error = -EINVAL;
+			break;
+		}
+
+		strcpy(opp->oprom_array, buf);
+		opp->oprom_size = len;
+
+		error = copyout(argp, opp, bufsize + sizeof(int));
+		break;
+
+	case OPROMU2P:
+	case OPROMGETCONS:
+	case OPROMGETFBNAME:
+		if (cnt++ < 10)
+			printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n");
+		error = -EINVAL;
+		break;
+	default:
+		if (cnt++ < 10)
+			printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
+		error = -EINVAL;
+		break;
+	}
+
+	kfree(opp);
+	return error;
+}
+
+
+/* Return nonzero if a specific node is in the PROM device tree. */
+static int intree(int root, int node)
+{
+	for (; root != 0; root = prom_getsibling(root))
+		if (root == node || intree(prom_getchild(root),node))
+			return 1;
+	return 0;
+}
+
+/* Return nonzero if a specific node is "valid". */
+static int goodnode(int n, DATA *data)
+{
+	if (n == data->lastnode || n == prom_root_node || n == options_node)
+		return 1;
+	if (n == 0 || n == -1 || !intree(prom_root_node,n))
+		return 0;
+	data->lastnode = n;
+	return 1;
+}
+
+/* Copy in a whole string from userspace into kernelspace. */
+static int copyin_string(char __user *user, size_t len, char **ptr)
+{
+	char *tmp;
+
+	if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
+		return -EINVAL;
+
+	tmp = kmalloc(len + 1, GFP_KERNEL);
+	if (!tmp)
+		return -ENOMEM;
+
+	if(copy_from_user(tmp, user, len)) {
+		kfree(tmp);
+		return -EFAULT;
+	}
+
+	tmp[len] = '\0';
+
+	*ptr = tmp;
+
+	return 0;
+}
+
+/*
+ *	NetBSD /dev/openprom ioctl calls.
+ */
+static int openprom_bsd_ioctl(struct inode * inode, struct file * file,
+			      unsigned int cmd, unsigned long arg)
+{
+	DATA *data = (DATA *) file->private_data;
+	void __user *argp = (void __user *)arg;
+	struct opiocdesc op;
+	int error, node, len;
+	char *str, *tmp;
+	char buffer[64];
+	static int cnt;
+
+	switch (cmd) {
+	case OPIOCGET:
+		if (copy_from_user(&op, argp, sizeof(op)))
+			return -EFAULT;
+
+		if (!goodnode(op.op_nodeid,data))
+			return -EINVAL;
+
+		error = copyin_string(op.op_name, op.op_namelen, &str);
+		if (error)
+			return error;
+
+		len = prom_getproplen(op.op_nodeid,str);
+
+		if (len > op.op_buflen) {
+			kfree(str);
+			return -ENOMEM;
+		}
+
+		op.op_buflen = len;
+
+		if (len <= 0) {
+			kfree(str);
+			/* Verified by the above copy_from_user */
+			if (__copy_to_user(argp, &op,
+				       sizeof(op)))
+				return -EFAULT;
+			return 0;
+		}
+
+		tmp = kmalloc(len + 1, GFP_KERNEL);
+		if (!tmp) {
+			kfree(str);
+			return -ENOMEM;
+		}
+
+		prom_getproperty(op.op_nodeid, str, tmp, len);
+
+		tmp[len] = '\0';
+
+		if (__copy_to_user(argp, &op, sizeof(op)) != 0
+		    || copy_to_user(op.op_buf, tmp, len) != 0)
+			error = -EFAULT;
+
+		kfree(tmp);
+		kfree(str);
+
+		return error;
+
+	case OPIOCNEXTPROP:
+		if (copy_from_user(&op, argp, sizeof(op)))
+			return -EFAULT;
+
+		if (!goodnode(op.op_nodeid,data))
+			return -EINVAL;
+
+		error = copyin_string(op.op_name, op.op_namelen, &str);
+		if (error)
+			return error;
+
+		tmp = prom_nextprop(op.op_nodeid,str,buffer);
+
+		if (tmp) {
+			len = strlen(tmp);
+			if (len > op.op_buflen)
+				len = op.op_buflen;
+			else
+				op.op_buflen = len;
+		} else {
+			len = op.op_buflen = 0;
+		}
+
+		if (!access_ok(VERIFY_WRITE, argp, sizeof(op))) {
+			kfree(str);
+			return -EFAULT;
+		}
+
+		if (!access_ok(VERIFY_WRITE, op.op_buf, len)) {
+			kfree(str);
+			return -EFAULT;
+		}
+
+		error = __copy_to_user(argp, &op, sizeof(op));
+		if (!error) error = __copy_to_user(op.op_buf, tmp, len);
+
+		kfree(str);
+
+		return error;
+
+	case OPIOCSET:
+		if (copy_from_user(&op, argp, sizeof(op)))
+			return -EFAULT;
+
+		if (!goodnode(op.op_nodeid,data))
+			return -EINVAL;
+
+		error = copyin_string(op.op_name, op.op_namelen, &str);
+		if (error)
+			return error;
+
+		error = copyin_string(op.op_buf, op.op_buflen, &tmp);
+		if (error) {
+			kfree(str);
+			return error;
+		}
+
+		len = prom_setprop(op.op_nodeid,str,tmp,op.op_buflen+1);
+
+		if (len != op.op_buflen)
+			return -EINVAL;
+
+		kfree(str);
+		kfree(tmp);
+
+		return 0;
+
+	case OPIOCGETOPTNODE:
+		if (copy_to_user(argp, &options_node, sizeof(int)))
+			return -EFAULT;
+		return 0;
+
+	case OPIOCGETNEXT:
+	case OPIOCGETCHILD:
+		if (copy_from_user(&node, argp, sizeof(int)))
+			return -EFAULT;
+
+		if (cmd == OPIOCGETNEXT)
+			node = __prom_getsibling(node);
+		else
+			node = __prom_getchild(node);
+
+		if (__copy_to_user(argp, &node, sizeof(int)))
+			return -EFAULT;
+
+		return 0;
+
+	default:
+		if (cnt++ < 10)
+			printk(KERN_INFO "openprom_bsd_ioctl: cmd 0x%X\n", cmd);
+		return -EINVAL;
+
+	}
+}
+
+
+/*
+ *	Handoff control to the correct ioctl handler.
+ */
+static int openprom_ioctl(struct inode * inode, struct file * file,
+			  unsigned int cmd, unsigned long arg)
+{
+	DATA *data = (DATA *) file->private_data;
+	static int cnt;
+
+	switch (cmd) {
+	case OPROMGETOPT:
+	case OPROMNXTOPT:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(inode, file, cmd, arg,
+					    options_node);
+
+	case OPROMSETOPT:
+	case OPROMSETOPT2:
+		if ((file->f_mode & FMODE_WRITE) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(inode, file, cmd, arg,
+					    options_node);
+
+	case OPROMNEXT:
+	case OPROMCHILD:
+	case OPROMGETPROP:
+	case OPROMNXTPROP:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(inode, file, cmd, arg,
+					    data->current_node);
+
+	case OPROMU2P:
+	case OPROMGETCONS:
+	case OPROMGETFBNAME:
+	case OPROMGETBOOTARGS:
+	case OPROMSETCUR:
+	case OPROMPCI2NODE:
+	case OPROMPATH2NODE:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(inode, file, cmd, arg, 0);
+
+	case OPIOCGET:
+	case OPIOCNEXTPROP:
+	case OPIOCGETOPTNODE:
+	case OPIOCGETNEXT:
+	case OPIOCGETCHILD:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EBADF;
+		return openprom_bsd_ioctl(inode,file,cmd,arg);
+
+	case OPIOCSET:
+		if ((file->f_mode & FMODE_WRITE) == 0)
+			return -EBADF;
+		return openprom_bsd_ioctl(inode,file,cmd,arg);
+
+	default:
+		if (cnt++ < 10)
+			printk("openprom_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg);
+		return -EINVAL;
+	}
+}
+
+static int openprom_open(struct inode * inode, struct file * file)
+{
+	DATA *data;
+
+	data = (DATA *) kmalloc(sizeof(DATA), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->current_node = prom_root_node;
+	data->lastnode = prom_root_node;
+	file->private_data = (void *)data;
+
+	return 0;
+}
+
+static int openprom_release(struct inode * inode, struct file * file)
+{
+	kfree(file->private_data);
+	return 0;
+}
+
+static struct file_operations openprom_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.ioctl =	openprom_ioctl,
+	.open =		openprom_open,
+	.release =	openprom_release,
+};
+
+static struct miscdevice openprom_dev = {
+	SUN_OPENPROM_MINOR, "openprom", &openprom_fops
+};
+
+static int __init openprom_init(void)
+{
+	int error;
+
+	error = misc_register(&openprom_dev);
+	if (error) {
+		printk(KERN_ERR "openprom: unable to get misc minor\n");
+		return error;
+	}
+
+	options_node = prom_getchild(prom_root_node);
+	options_node = prom_searchsiblings(options_node,"options");
+
+	if (options_node == 0 || options_node == -1) {
+		printk(KERN_ERR "openprom: unable to find options node\n");
+		misc_deregister(&openprom_dev);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static void __exit openprom_cleanup(void)
+{
+	misc_deregister(&openprom_dev);
+}
+
+module_init(openprom_init);
+module_exit(openprom_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/riowatchdog.c b/drivers/sbus/char/riowatchdog.c
new file mode 100644
index 0000000..d1babff
--- /dev/null
+++ b/drivers/sbus/char/riowatchdog.c
@@ -0,0 +1,293 @@
+/* $Id: riowatchdog.c,v 1.3.2.2 2002/01/23 18:48:02 davem Exp $
+ * riowatchdog.c - driver for hw watchdog inside Super I/O of RIO
+ *
+ * Copyright (C) 2001 David S. Miller (davem@redhat.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+
+#include <asm/io.h>
+#include <asm/ebus.h>
+#include <asm/bbc.h>
+#include <asm/oplib.h>
+#include <asm/uaccess.h>
+
+#include <asm/watchdog.h>
+
+/* RIO uses the NatSemi Super I/O power management logical device
+ * as its' watchdog.
+ *
+ * When the watchdog triggers, it asserts a line to the BBC (Boot Bus
+ * Controller) of the machine.  The BBC can only be configured to
+ * trigger a power-on reset when the signal is asserted.  The BBC
+ * can be configured to ignore the signal entirely as well.
+ *
+ * The only Super I/O device register we care about is at index
+ * 0x05 (WDTO_INDEX) which is the watchdog time-out in minutes (1-255).
+ * If set to zero, this disables the watchdog.  When set, the system
+ * must periodically (before watchdog expires) clear (set to zero) and
+ * re-set the watchdog else it will trigger.
+ *
+ * There are two other indexed watchdog registers inside this Super I/O
+ * logical device, but they are unused.  The first, at index 0x06 is
+ * the watchdog control and can be used to make the watchdog timer re-set
+ * when the PS/2 mouse or serial lines show activity.  The second, at
+ * index 0x07 is merely a sampling of the line from the watchdog to the
+ * BBC.
+ *
+ * The watchdog device generates no interrupts.
+ */
+
+MODULE_AUTHOR("David S. Miller <davem@redhat.com>");
+MODULE_DESCRIPTION("Hardware watchdog driver for Sun RIO");
+MODULE_SUPPORTED_DEVICE("watchdog");
+MODULE_LICENSE("GPL");
+
+#define RIOWD_NAME	"pmc"
+#define RIOWD_MINOR	215
+
+static DEFINE_SPINLOCK(riowd_lock);
+
+static void __iomem *bbc_regs;
+static void __iomem *riowd_regs;
+#define WDTO_INDEX	0x05
+
+static int riowd_timeout = 1;		/* in minutes */
+module_param(riowd_timeout, int, 0);
+MODULE_PARM_DESC(riowd_timeout, "Watchdog timeout in minutes");
+
+#if 0 /* Currently unused. */
+static u8 riowd_readreg(int index)
+{
+	unsigned long flags;
+	u8 ret;
+
+	spin_lock_irqsave(&riowd_lock, flags);
+	writeb(index, riowd_regs + 0);
+	ret = readb(riowd_regs + 1);
+	spin_unlock_irqrestore(&riowd_lock, flags);
+
+	return ret;
+}
+#endif
+
+static void riowd_writereg(u8 val, int index)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&riowd_lock, flags);
+	writeb(index, riowd_regs + 0);
+	writeb(val, riowd_regs + 1);
+	spin_unlock_irqrestore(&riowd_lock, flags);
+}
+
+static void riowd_pingtimer(void)
+{
+	riowd_writereg(riowd_timeout, WDTO_INDEX);
+}
+
+static void riowd_stoptimer(void)
+{
+	u8 val;
+
+	riowd_writereg(0, WDTO_INDEX);
+
+	val = readb(bbc_regs + BBC_WDACTION);
+	val &= ~BBC_WDACTION_RST;
+	writeb(val, bbc_regs + BBC_WDACTION);
+}
+
+static void riowd_starttimer(void)
+{
+	u8 val;
+
+	riowd_writereg(riowd_timeout, WDTO_INDEX);
+
+	val = readb(bbc_regs + BBC_WDACTION);
+	val |= BBC_WDACTION_RST;
+	writeb(val, bbc_regs + BBC_WDACTION);
+}
+
+static int riowd_open(struct inode *inode, struct file *filp)
+{
+	nonseekable_open(inode, filp);
+	return 0;
+}
+
+static int riowd_release(struct inode *inode, struct file *filp)
+{
+	return 0;
+}
+
+static int riowd_ioctl(struct inode *inode, struct file *filp,
+		       unsigned int cmd, unsigned long arg)
+{
+	static struct watchdog_info info = {
+	       	WDIOF_SETTIMEOUT, 0, "Natl. Semiconductor PC97317"
+	};
+	void __user *argp = (void __user *)arg;
+	unsigned int options;
+	int new_margin;
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user(argp, &info, sizeof(info)))
+			return -EFAULT;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		if (put_user(0, (int __user *)argp))
+			return -EFAULT;
+		break;
+
+	case WDIOC_KEEPALIVE:
+		riowd_pingtimer();
+		break;
+
+	case WDIOC_SETOPTIONS:
+		if (copy_from_user(&options, argp, sizeof(options)))
+			return -EFAULT;
+
+		if (options & WDIOS_DISABLECARD)
+			riowd_stoptimer();
+		else if (options & WDIOS_ENABLECARD)
+			riowd_starttimer();
+		else
+			return -EINVAL;
+
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		if (get_user(new_margin, (int __user *)argp))
+			return -EFAULT;
+		if ((new_margin < 60) || (new_margin > (255 * 60)))
+		    return -EINVAL;
+		riowd_timeout = (new_margin + 59) / 60;
+		riowd_pingtimer();
+		/* Fall */
+
+	case WDIOC_GETTIMEOUT:
+		return put_user(riowd_timeout * 60, (int __user *)argp);
+
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+static ssize_t riowd_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
+{
+	if (count) {
+		riowd_pingtimer();
+		return 1;
+	}
+
+	return 0;
+}
+
+static struct file_operations riowd_fops = {
+	.owner =	THIS_MODULE,
+	.ioctl =	riowd_ioctl,
+	.open =		riowd_open,
+	.write =	riowd_write,
+	.release =	riowd_release,
+};
+
+static struct miscdevice riowd_miscdev = { RIOWD_MINOR, RIOWD_NAME, &riowd_fops };
+
+static int __init riowd_bbc_init(void)
+{
+	struct 	linux_ebus *ebus = NULL;
+	struct 	linux_ebus_device *edev = NULL;
+	u8 val;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, "bbc"))
+				goto found_bbc;
+		}
+	}
+
+found_bbc:
+	if (!edev)
+		return -ENODEV;
+	bbc_regs = ioremap(edev->resource[0].start, BBC_REGS_SIZE);
+	if (!bbc_regs)
+		return -ENODEV;
+
+	/* Turn it off. */
+	val = readb(bbc_regs + BBC_WDACTION);
+	val &= ~BBC_WDACTION_RST;
+	writeb(val, bbc_regs + BBC_WDACTION);
+
+	return 0;
+}
+
+static int __init riowd_init(void)
+{
+	struct 	linux_ebus *ebus = NULL;
+	struct 	linux_ebus_device *edev = NULL;
+
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			if (!strcmp(edev->prom_name, RIOWD_NAME))
+				goto ebus_done;
+		}
+	}
+
+ebus_done:
+	if (!edev)
+		goto fail;
+
+	riowd_regs = ioremap(edev->resource[0].start, 2);
+	if (riowd_regs == NULL) {
+		printk(KERN_ERR "pmc: Cannot map registers.\n");
+		return -ENODEV;
+	}
+
+	if (riowd_bbc_init()) {
+		printk(KERN_ERR "pmc: Failure initializing BBC config.\n");
+		goto fail;
+	}
+
+	if (misc_register(&riowd_miscdev)) {
+		printk(KERN_ERR "pmc: Cannot register watchdog misc device.\n");
+		goto fail;
+	}
+
+	printk(KERN_INFO "pmc: Hardware watchdog [%i minutes], "
+	       "regs at %p\n", riowd_timeout, riowd_regs);
+
+	return 0;
+
+fail:
+	if (riowd_regs) {
+		iounmap(riowd_regs);
+		riowd_regs = NULL;
+	}
+	if (bbc_regs) {
+		iounmap(bbc_regs);
+		bbc_regs = NULL;
+	}
+	return -ENODEV;
+}
+
+static void __exit riowd_cleanup(void)
+{
+	misc_deregister(&riowd_miscdev);
+	iounmap(riowd_regs);
+	riowd_regs = NULL;
+	iounmap(bbc_regs);
+	bbc_regs = NULL;
+}
+
+module_init(riowd_init);
+module_exit(riowd_cleanup);
diff --git a/drivers/sbus/char/rtc.c b/drivers/sbus/char/rtc.c
new file mode 100644
index 0000000..bf3273e
--- /dev/null
+++ b/drivers/sbus/char/rtc.c
@@ -0,0 +1,178 @@
+/* $Id: rtc.c,v 1.28 2001/10/08 22:19:51 davem Exp $
+ *
+ * Linux/SPARC Real Time Clock Driver
+ * Copyright (C) 1996 Thomas K. Dyas (tdyas@eden.rutgers.edu)
+ *
+ * This is a little driver that lets a user-level program access
+ * the SPARC Mostek real time clock chip. It is no use unless you
+ * use the modified clock utility.
+ *
+ * Get the modified clock utility from:
+ *   ftp://vger.kernel.org/pub/linux/Sparc/userland/clock.c
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/slab.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/smp_lock.h>
+#include <asm/io.h>
+#include <asm/mostek.h>
+#include <asm/system.h>
+#include <asm/uaccess.h>
+#include <asm/rtc.h>
+
+static int rtc_busy = 0;
+
+/* Retrieve the current date and time from the real time clock. */
+static void get_rtc_time(struct rtc_time *t)
+{
+	void * __iomem regs = mstk48t02_regs;
+	u8 tmp;
+
+	spin_lock_irq(&mostek_lock);
+
+	tmp = mostek_read(regs + MOSTEK_CREG);
+	tmp |= MSTK_CREG_READ;
+	mostek_write(regs + MOSTEK_CREG, tmp);
+
+	t->sec = MSTK_REG_SEC(regs);
+	t->min = MSTK_REG_MIN(regs);
+	t->hour = MSTK_REG_HOUR(regs);
+	t->dow = MSTK_REG_DOW(regs);
+	t->dom = MSTK_REG_DOM(regs);
+	t->month = MSTK_REG_MONTH(regs);
+	t->year = MSTK_CVT_YEAR( MSTK_REG_YEAR(regs) );
+
+	tmp = mostek_read(regs + MOSTEK_CREG);
+	tmp &= ~MSTK_CREG_READ;
+	mostek_write(regs + MOSTEK_CREG, tmp);
+
+	spin_unlock_irq(&mostek_lock);
+}
+
+/* Set the current date and time inthe real time clock. */
+void set_rtc_time(struct rtc_time *t)
+{
+	void * __iomem regs = mstk48t02_regs;
+	u8 tmp;
+
+	spin_lock_irq(&mostek_lock);
+
+	tmp = mostek_read(regs + MOSTEK_CREG);
+	tmp |= MSTK_CREG_WRITE;
+	mostek_write(regs + MOSTEK_CREG, tmp);
+
+	MSTK_SET_REG_SEC(regs,t->sec);
+	MSTK_SET_REG_MIN(regs,t->min);
+	MSTK_SET_REG_HOUR(regs,t->hour);
+	MSTK_SET_REG_DOW(regs,t->dow);
+	MSTK_SET_REG_DOM(regs,t->dom);
+	MSTK_SET_REG_MONTH(regs,t->month);
+	MSTK_SET_REG_YEAR(regs,t->year - MSTK_YEAR_ZERO);
+
+	tmp = mostek_read(regs + MOSTEK_CREG);
+	tmp &= ~MSTK_CREG_WRITE;
+	mostek_write(regs + MOSTEK_CREG, tmp);
+
+	spin_unlock_irq(&mostek_lock);
+}
+
+static int rtc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	unsigned long arg)
+{
+	struct rtc_time rtc_tm;
+	void __user *argp = (void __user *)arg;
+
+	switch (cmd)
+	{
+	case RTCGET:
+		memset(&rtc_tm, 0, sizeof(struct rtc_time));
+		get_rtc_time(&rtc_tm);
+
+		if (copy_to_user(argp, &rtc_tm, sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		return 0;
+
+
+	case RTCSET:
+		if (!capable(CAP_SYS_TIME))
+			return -EPERM;
+
+		if (copy_from_user(&rtc_tm, argp, sizeof(struct rtc_time)))
+			return -EFAULT;
+
+		set_rtc_time(&rtc_tm);
+
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int rtc_open(struct inode *inode, struct file *file)
+{
+	int ret;
+
+	spin_lock_irq(&mostek_lock);
+	if (rtc_busy) {
+		ret = -EBUSY;
+	} else {
+		rtc_busy = 1;
+		ret = 0;
+	}
+	spin_unlock_irq(&mostek_lock);
+
+	return ret;
+}
+
+static int rtc_release(struct inode *inode, struct file *file)
+{
+	rtc_busy = 0;
+
+	return 0;
+}
+
+static struct file_operations rtc_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.ioctl =	rtc_ioctl,
+	.open =		rtc_open,
+	.release =	rtc_release,
+};
+
+static struct miscdevice rtc_dev = { RTC_MINOR, "rtc", &rtc_fops };
+
+static int __init rtc_sun_init(void)
+{
+	int error;
+
+	/* It is possible we are being driven by some other RTC chip
+	 * and thus another RTC driver is handling things.
+	 */
+	if (mstk48t02_regs == 0)
+		return -ENODEV;
+
+	error = misc_register(&rtc_dev);
+	if (error) {
+		printk(KERN_ERR "rtc: unable to get misc minor for Mostek\n");
+		return error;
+	}
+
+	return 0;
+}
+
+static void __exit rtc_sun_cleanup(void)
+{
+	misc_deregister(&rtc_dev);
+}
+
+module_init(rtc_sun_init);
+module_exit(rtc_sun_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c
new file mode 100644
index 0000000..858cc68
--- /dev/null
+++ b/drivers/sbus/char/uctrl.c
@@ -0,0 +1,422 @@
+/* $Id: uctrl.c,v 1.12 2001/10/08 22:19:51 davem Exp $
+ * uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
+ *
+ * Copyright 1999 Derrick J Brashear (shadow@dementia.org)
+ */
+
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/system.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+#include <asm/sbus.h>
+
+#define UCTRL_MINOR	174
+
+#define DEBUG 1
+#ifdef DEBUG
+#define dprintk(x) printk x
+#else
+#define dprintk(x)
+#endif
+
+struct uctrl_regs {
+	volatile u32 uctrl_intr;
+	volatile u32 uctrl_data;
+	volatile u32 uctrl_stat;
+	volatile u32 uctrl_xxx[5];
+};
+
+struct ts102_regs {
+	volatile u32 card_a_intr;
+	volatile u32 card_a_stat;
+	volatile u32 card_a_ctrl;
+	volatile u32 card_a_xxx;
+	volatile u32 card_b_intr;
+	volatile u32 card_b_stat;
+	volatile u32 card_b_ctrl;
+	volatile u32 card_b_xxx;
+	volatile u32 uctrl_intr;
+	volatile u32 uctrl_data;
+	volatile u32 uctrl_stat;
+	volatile u32 uctrl_xxx;
+	volatile u32 ts102_xxx[4];
+};
+
+/* Bits for uctrl_intr register */
+#define UCTRL_INTR_TXE_REQ         0x01    /* transmit FIFO empty int req */
+#define UCTRL_INTR_TXNF_REQ        0x02    /* transmit FIFO not full int req */
+#define UCTRL_INTR_RXNE_REQ        0x04    /* receive FIFO not empty int req */
+#define UCTRL_INTR_RXO_REQ         0x08    /* receive FIFO overflow int req */
+#define UCTRL_INTR_TXE_MSK         0x10    /* transmit FIFO empty mask */
+#define UCTRL_INTR_TXNF_MSK        0x20    /* transmit FIFO not full mask */
+#define UCTRL_INTR_RXNE_MSK        0x40    /* receive FIFO not empty mask */
+#define UCTRL_INTR_RXO_MSK         0x80    /* receive FIFO overflow mask */
+
+/* Bits for uctrl_stat register */
+#define UCTRL_STAT_TXE_STA         0x01    /* transmit FIFO empty status */
+#define UCTRL_STAT_TXNF_STA        0x02    /* transmit FIFO not full status */
+#define UCTRL_STAT_RXNE_STA        0x04    /* receive FIFO not empty status */
+#define UCTRL_STAT_RXO_STA         0x08    /* receive FIFO overflow status */
+
+static const char *uctrl_extstatus[16] = {
+        "main power available",
+        "internal battery attached",
+        "external battery attached",
+        "external VGA attached",
+        "external keyboard attached",
+        "external mouse attached",
+        "lid down",
+        "internal battery currently charging",
+        "external battery currently charging",
+        "internal battery currently discharging",
+        "external battery currently discharging",
+};
+
+/* Everything required for one transaction with the uctrl */
+struct uctrl_txn {
+	u8 opcode;
+	u8 inbits;
+	u8 outbits;
+	u8 *inbuf;
+	u8 *outbuf;
+};
+
+struct uctrl_status {
+	u8 current_temp; /* 0x07 */
+	u8 reset_status; /* 0x0b */
+	u16 event_status; /* 0x0c */
+	u16 error_status; /* 0x10 */
+	u16 external_status; /* 0x11, 0x1b */
+	u8 internal_charge; /* 0x18 */
+	u8 external_charge; /* 0x19 */
+	u16 control_lcd; /* 0x20 */
+	u8 control_bitport; /* 0x21 */
+	u8 speaker_volume; /* 0x23 */
+	u8 control_tft_brightness; /* 0x24 */
+	u8 control_kbd_repeat_delay; /* 0x28 */
+	u8 control_kbd_repeat_period; /* 0x29 */
+	u8 control_screen_contrast; /* 0x2F */
+};
+
+enum uctrl_opcode {
+  READ_SERIAL_NUMBER=0x1,
+  READ_ETHERNET_ADDRESS=0x2,
+  READ_HARDWARE_VERSION=0x3,
+  READ_MICROCONTROLLER_VERSION=0x4,
+  READ_MAX_TEMPERATURE=0x5,
+  READ_MIN_TEMPERATURE=0x6,
+  READ_CURRENT_TEMPERATURE=0x7,
+  READ_SYSTEM_VARIANT=0x8,
+  READ_POWERON_CYCLES=0x9,
+  READ_POWERON_SECONDS=0xA,
+  READ_RESET_STATUS=0xB,
+  READ_EVENT_STATUS=0xC,
+  READ_REAL_TIME_CLOCK=0xD,
+  READ_EXTERNAL_VGA_PORT=0xE,
+  READ_MICROCONTROLLER_ROM_CHECKSUM=0xF,
+  READ_ERROR_STATUS=0x10,
+  READ_EXTERNAL_STATUS=0x11,
+  READ_USER_CONFIGURATION_AREA=0x12,
+  READ_MICROCONTROLLER_VOLTAGE=0x13,
+  READ_INTERNAL_BATTERY_VOLTAGE=0x14,
+  READ_DCIN_VOLTAGE=0x15,
+  READ_HORIZONTAL_POINTER_VOLTAGE=0x16,
+  READ_VERTICAL_POINTER_VOLTAGE=0x17,
+  READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18,
+  READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19,
+  READ_REAL_TIME_CLOCK_ALARM=0x1A,
+  READ_EVENT_STATUS_NO_RESET=0x1B,
+  READ_INTERNAL_KEYBOARD_LAYOUT=0x1C,
+  READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D,
+  READ_EEPROM_STATUS=0x1E,
+  CONTROL_LCD=0x20,
+  CONTROL_BITPORT=0x21,
+  SPEAKER_VOLUME=0x23,
+  CONTROL_TFT_BRIGHTNESS=0x24,
+  CONTROL_WATCHDOG=0x25,
+  CONTROL_FACTORY_EEPROM_AREA=0x26,
+  CONTROL_KBD_TIME_UNTIL_REPEAT=0x28,
+  CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29,
+  CONTROL_TIMEZONE=0x2A,
+  CONTROL_MARK_SPACE_RATIO=0x2B,
+  CONTROL_DIAGNOSTIC_MODE=0x2E,
+  CONTROL_SCREEN_CONTRAST=0x2F,
+  RING_BELL=0x30,
+  SET_DIAGNOSTIC_STATUS=0x32,
+  CLEAR_KEY_COMBINATION_TABLE=0x33,
+  PERFORM_SOFTWARE_RESET=0x34,
+  SET_REAL_TIME_CLOCK=0x35,
+  RECALIBRATE_POINTING_STICK=0x36,
+  SET_BELL_FREQUENCY=0x37,
+  SET_INTERNAL_BATTERY_CHARGE_RATE=0x39,
+  SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A,
+  SET_REAL_TIME_CLOCK_ALARM=0x3B,
+  READ_EEPROM=0x40,
+  WRITE_EEPROM=0x41,
+  WRITE_TO_STATUS_DISPLAY=0x42,
+  DEFINE_SPECIAL_CHARACTER=0x43,
+  DEFINE_KEY_COMBINATION_ENTRY=0x50,
+  DEFINE_STRING_TABLE_ENTRY=0x51,
+  DEFINE_STATUS_SCREEN_DISPLAY=0x52,
+  PERFORM_EMU_COMMANDS=0x64,
+  READ_EMU_REGISTER=0x65,
+  WRITE_EMU_REGISTER=0x66,
+  READ_EMU_RAM=0x67,
+  WRITE_EMU_RAM=0x68,
+  READ_BQ_REGISTER=0x69,
+  WRITE_BQ_REGISTER=0x6A,
+  SET_USER_PASSWORD=0x70,
+  VERIFY_USER_PASSWORD=0x71,
+  GET_SYSTEM_PASSWORD_KEY=0x72,
+  VERIFY_SYSTEM_PASSWORD=0x73,
+  POWER_OFF=0x82,
+  POWER_RESTART=0x83,
+};
+
+struct uctrl_driver {
+	struct uctrl_regs *regs;
+	int irq;
+	int pending;
+	struct uctrl_status status;
+};
+
+static struct uctrl_driver drv;
+
+void uctrl_get_event_status(void);
+void uctrl_get_external_status(void);
+
+static int
+uctrl_ioctl(struct inode *inode, struct file *file,
+	      unsigned int cmd, unsigned long arg)
+{
+	switch (cmd) {
+		default:
+			return -EINVAL;
+	}
+	return 0;
+}
+
+static int
+uctrl_open(struct inode *inode, struct file *file)
+{
+	uctrl_get_event_status();
+	uctrl_get_external_status();
+	return 0;
+}
+
+static irqreturn_t uctrl_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	struct uctrl_driver *driver = (struct uctrl_driver *)dev_id;
+	printk("in uctrl_interrupt\n");
+	return IRQ_HANDLED;
+}
+
+static struct file_operations uctrl_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.ioctl =	uctrl_ioctl,
+	.open =		uctrl_open,
+};
+
+static struct miscdevice uctrl_dev = {
+	UCTRL_MINOR,
+	"uctrl",
+	&uctrl_fops
+};
+
+/* Wait for space to write, then write to it */
+#define WRITEUCTLDATA(value) \
+{ \
+  unsigned int i; \
+  for (i = 0; i < 10000; i++) { \
+    if (UCTRL_STAT_TXNF_STA & driver->regs->uctrl_stat) \
+      break; \
+  } \
+  dprintk(("write data 0x%02x\n", value)); \
+  driver->regs->uctrl_data = value; \
+}
+
+/* Wait for something to read, read it, then clear the bit */
+#define READUCTLDATA(value) \
+{ \
+  unsigned int i; \
+  value = 0; \
+  for (i = 0; i < 10000; i++) { \
+    if ((UCTRL_STAT_RXNE_STA & driver->regs->uctrl_stat) == 0) \
+      break; \
+    udelay(1); \
+  } \
+  value = driver->regs->uctrl_data; \
+  dprintk(("read data 0x%02x\n", value)); \
+  driver->regs->uctrl_stat = UCTRL_STAT_RXNE_STA; \
+}
+
+void uctrl_set_video(int status)
+{
+	struct uctrl_driver *driver = &drv;
+	
+}
+
+static void uctrl_do_txn(struct uctrl_txn *txn)
+{
+	struct uctrl_driver *driver = &drv;
+	int stat, incnt, outcnt, bytecnt, intr;
+	u32 byte;
+
+	stat = driver->regs->uctrl_stat;
+	intr = driver->regs->uctrl_intr;
+	driver->regs->uctrl_stat = stat;
+
+	dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr));
+
+	incnt = txn->inbits;
+	outcnt = txn->outbits;
+	byte = (txn->opcode << 8);
+	WRITEUCTLDATA(byte);
+
+	bytecnt = 0;
+	while (incnt > 0) {
+		byte = (txn->inbuf[bytecnt] << 8);
+		WRITEUCTLDATA(byte);
+		incnt--;
+		bytecnt++;
+	}
+
+	/* Get the ack */
+	READUCTLDATA(byte);
+	dprintk(("ack was %x\n", (byte >> 8)));
+
+	bytecnt = 0;
+	while (outcnt > 0) {
+		READUCTLDATA(byte);
+		txn->outbuf[bytecnt] = (byte >> 8);
+		dprintk(("set byte to %02x\n", byte));
+		outcnt--;
+		bytecnt++;
+	}
+}
+
+void uctrl_get_event_status()
+{
+	struct uctrl_driver *driver = &drv;
+	struct uctrl_txn txn;
+	u8 outbits[2];
+
+	txn.opcode = READ_EVENT_STATUS;
+	txn.inbits = 0;
+	txn.outbits = 2;
+	txn.inbuf = 0;
+	txn.outbuf = outbits;
+
+	uctrl_do_txn(&txn);
+
+	dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
+	driver->status.event_status = 
+		((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff);
+	dprintk(("ev is %x\n", driver->status.event_status));
+}
+
+void uctrl_get_external_status()
+{
+	struct uctrl_driver *driver = &drv;
+	struct uctrl_txn txn;
+	u8 outbits[2];
+	int i, v;
+
+	txn.opcode = READ_EXTERNAL_STATUS;
+	txn.inbits = 0;
+	txn.outbits = 2;
+	txn.inbuf = 0;
+	txn.outbuf = outbits;
+
+	uctrl_do_txn(&txn);
+
+	dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff)));
+	driver->status.external_status = 
+		((outbits[0] * 256) + (outbits[1]));
+	dprintk(("ex is %x\n", driver->status.external_status));
+	v = driver->status.external_status;
+	for (i = 0; v != 0; i++, v >>= 1) {
+		if (v & 1) {
+			dprintk(("%s%s", " ", uctrl_extstatus[i]));
+		}
+	}
+	dprintk(("\n"));
+	
+}
+
+static int __init ts102_uctrl_init(void)
+{
+	struct uctrl_driver *driver = &drv;
+	int len, i;
+	struct linux_prom_irqs tmp_irq[2];
+        unsigned int vaddr[2] = { 0, 0 };
+	int tmpnode, uctrlnode = prom_getchild(prom_root_node);
+
+	tmpnode = prom_searchsiblings(uctrlnode, "obio");
+
+	if (tmpnode)
+	  uctrlnode = prom_getchild(tmpnode);
+
+	uctrlnode = prom_searchsiblings(uctrlnode, "uctrl");
+
+	if (!uctrlnode)
+		return -ENODEV;
+
+	/* the prom mapped it for us */
+	len = prom_getproperty(uctrlnode, "address", (void *) vaddr,
+			       sizeof(vaddr));
+	driver->regs = (struct uctrl_regs *)vaddr[0];
+
+	len = prom_getproperty(uctrlnode, "intr", (char *) tmp_irq,
+			       sizeof(tmp_irq));
+
+	/* Flush device */
+	READUCTLDATA(len);
+
+	if(!driver->irq) 
+		driver->irq = tmp_irq[0].pri;
+
+	request_irq(driver->irq, uctrl_interrupt, 0, "uctrl", driver);
+
+	if (misc_register(&uctrl_dev)) {
+		printk("%s: unable to get misc minor %d\n",
+		       __FUNCTION__, uctrl_dev.minor);
+		free_irq(driver->irq, driver);
+		return -ENODEV;
+	}
+
+	driver->regs->uctrl_intr = UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK;
+	printk("uctrl: 0x%x (irq %s)\n", driver->regs, __irq_itoa(driver->irq));
+	uctrl_get_event_status();
+	uctrl_get_external_status();
+        return 0;
+}
+
+static void __exit ts102_uctrl_cleanup(void)
+{
+	struct uctrl_driver *driver = &drv;
+
+	misc_deregister(&uctrl_dev);
+	if (driver->irq)
+		free_irq(driver->irq, driver);
+	if (driver->regs)
+		driver->regs = 0;
+}
+
+module_init(ts102_uctrl_init);
+module_exit(ts102_uctrl_cleanup);
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/vfc.h b/drivers/sbus/char/vfc.h
new file mode 100644
index 0000000..e56a43a
--- /dev/null
+++ b/drivers/sbus/char/vfc.h
@@ -0,0 +1,179 @@
+#ifndef _LINUX_VFC_H_
+#define _LINUX_VFC_H_
+
+#include <linux/devfs_fs_kernel.h>
+
+/*
+ * The control register for the vfc is at offset 0x4000
+ * The first field ram bank is located at offset 0x5000
+ * The second field ram bank is at offset 0x7000
+ * i2c_reg address the Phillips PCF8584(see notes in vfc_i2c.c) 
+ *    data and transmit register.
+ * i2c_s1 controls register s1 of the PCF8584
+ * i2c_write seems to be similar to i2c_write but I am not 
+ *    quite sure why sun uses it
+ * 
+ * I am also not sure whether or not you can read the fram bank as a
+ * whole or whether you must read each word individually from offset
+ * 0x5000 as soon as I figure it out I will update this file */
+
+struct vfc_regs {
+	char pad1[0x4000];
+	unsigned int control;  /* Offset 0x4000 */
+	char pad2[0xffb];      /* from offset 0x4004 to 0x5000 */
+	unsigned int fram_bank1; /* Offset 0x5000 */
+	char pad3[0xffb];        /* from offset 0x5004 to 0x6000 */
+	unsigned int i2c_reg; /* Offset 0x6000 */
+	unsigned int i2c_magic2; /* Offset 0x6004 */
+	unsigned int i2c_s1;  /* Offset 0x6008 */
+	unsigned int i2c_write; /* Offset 0x600c */
+	char pad4[0xff0];     /* from offset 0x6010 to 0x7000 */
+	unsigned int fram_bank2; /* Offset 0x7000 */
+	char pad5[0x1000];
+};
+
+#define VFC_SAA9051_NR (13)
+#define VFC_SAA9051_ADDR (0x8a)
+	/* The saa9051 returns the following for its status 
+	 * bit 0 - 0
+	 * bit 1 - SECAM color detected (1=found,0=not found)
+	 * bit 2 - COLOR detected (1=found,0=not found)
+	 * bit 3 - 0
+	 * bit 4 - Field frequency bit (1=60Hz (NTSC), 0=50Hz (PAL))
+	 * bit 5 - 1
+	 * bit 6 - horizontal frequency lock (1=transmitter found,
+	 *                                    0=no transmitter)
+	 * bit 7 - Power on reset bit (1=reset,0=at least one successful 
+	 *                                       read of the status byte)
+	 */
+
+#define VFC_SAA9051_PONRES (0x80)
+#define VFC_SAA9051_HLOCK (0x40)
+#define VFC_SAA9051_FD (0x10)
+#define VFC_SAA9051_CD (0x04)
+#define VFC_SAA9051_CS (0x02)
+
+
+/* The various saa9051 sub addresses */
+
+#define VFC_SAA9051_IDEL (0) 
+#define VFC_SAA9051_HSY_START (1)
+#define VFC_SAA9051_HSY_STOP (2)
+#define VFC_SAA9051_HC_START (3)
+#define VFC_SAA9051_HC_STOP (4)
+#define VFC_SAA9051_HS_START (5)
+#define VFC_SAA9051_HORIZ_PEAK (6)
+#define VFC_SAA9051_HUE (7)
+#define VFC_SAA9051_C1 (8)
+#define VFC_SAA9051_C2 (9)
+#define VFC_SAA9051_C3 (0xa)
+#define VFC_SAA9051_SECAM_DELAY (0xb)
+
+
+/* Bit settings for saa9051 sub address 0x06 */
+
+#define VFC_SAA9051_AP1 (0x01)
+#define VFC_SAA9051_AP2 (0x02)
+#define VFC_SAA9051_COR1 (0x04)
+#define VFC_SAA9051_COR2 (0x08)
+#define VFC_SAA9051_BP1 (0x10)
+#define VFC_SAA9051_BP2 (0x20)
+#define VFC_SAA9051_PF (0x40)
+#define VFC_SAA9051_BY (0x80)
+
+
+/* Bit settings for saa9051 sub address 0x08 */
+
+#define VFC_SAA9051_CCFR0 (0x01)
+#define VFC_SAA9051_CCFR1 (0x02)
+#define VFC_SAA9051_YPN (0x04)
+#define VFC_SAA9051_ALT (0x08)
+#define VFC_SAA9051_CO (0x10)
+#define VFC_SAA9051_VTR (0x20)
+#define VFC_SAA9051_FS (0x40)
+#define VFC_SAA9051_HPLL (0x80)
+
+
+/* Bit settings for saa9051 sub address 9 */
+
+#define VFC_SAA9051_SS0 (0x01)
+#define VFC_SAA9051_SS1 (0x02)
+#define VFC_SAA9051_AFCC (0x04)
+#define VFC_SAA9051_CI (0x08)
+#define VFC_SAA9051_SA9D4 (0x10) /* Don't care bit */
+#define VFC_SAA9051_OEC (0x20)
+#define VFC_SAA9051_OEY (0x40)
+#define VFC_SAA9051_VNL (0x80)
+
+
+/* Bit settings for saa9051 sub address 0x0A */
+
+#define VFC_SAA9051_YDL0 (0x01)
+#define VFC_SAA9051_YDL1 (0x02)
+#define VFC_SAA9051_YDL2 (0x04)
+#define VFC_SAA9051_SS2 (0x08)
+#define VFC_SAA9051_SS3 (0x10)
+#define VFC_SAA9051_YC (0x20)
+#define VFC_SAA9051_CT (0x40)
+#define VFC_SAA9051_SYC (0x80)
+
+
+#define VFC_SAA9051_SA(a,b) ((a)->saa9051_state_array[(b)+1])
+#define vfc_update_saa9051(a) (vfc_i2c_sendbuf((a),VFC_SAA9051_ADDR,\
+					    (a)->saa9051_state_array,\
+					    VFC_SAA9051_NR))
+
+
+struct vfc_dev {
+	volatile struct vfc_regs *regs;
+	struct vfc_regs *phys_regs;
+	unsigned int control_reg;
+	struct semaphore device_lock_sem;
+	struct timer_list poll_timer;
+	wait_queue_head_t poll_wait;
+	int instance;
+	int busy;
+	unsigned long which_io;
+	unsigned char saa9051_state_array[VFC_SAA9051_NR];
+};
+
+extern struct vfc_dev **vfc_dev_lst;
+
+void captstat_reset(struct vfc_dev *);
+void memptr_reset(struct vfc_dev *);
+
+int vfc_pcf8584_init(struct vfc_dev *);
+void vfc_i2c_delay_no_busy(struct vfc_dev *, unsigned long);
+void vfc_i2c_delay(struct vfc_dev *);
+int vfc_i2c_sendbuf(struct vfc_dev *, unsigned char, char *, int) ;
+int vfc_i2c_recvbuf(struct vfc_dev *, unsigned char, char *, int) ;
+int vfc_i2c_reset_bus(struct vfc_dev *);
+int vfc_init_i2c_bus(struct vfc_dev *);
+void vfc_lock_device(struct vfc_dev *);
+void vfc_unlock_device(struct vfc_dev *);
+
+#define VFC_CONTROL_DIAGMODE  0x10000000
+#define VFC_CONTROL_MEMPTR    0x20000000
+#define VFC_CONTROL_CAPTURE   0x02000000
+#define VFC_CONTROL_CAPTRESET 0x04000000
+
+#define VFC_STATUS_CAPTURE    0x08000000
+
+#ifdef VFC_IOCTL_DEBUG
+#define VFC_IOCTL_DEBUG_PRINTK(a) printk a
+#else
+#define VFC_IOCTL_DEBUG_PRINTK(a)
+#endif
+
+#ifdef VFC_I2C_DEBUG
+#define VFC_I2C_DEBUG_PRINTK(a) printk a
+#else
+#define VFC_I2C_DEBUG_PRINTK(a)
+#endif
+
+#endif /* _LINUX_VFC_H_ */
+
+
+
+
+
diff --git a/drivers/sbus/char/vfc_dev.c b/drivers/sbus/char/vfc_dev.c
new file mode 100644
index 0000000..86ce541
--- /dev/null
+++ b/drivers/sbus/char/vfc_dev.c
@@ -0,0 +1,742 @@
+/*
+ * drivers/sbus/char/vfc_dev.c
+ *
+ * Driver for the Videopix Frame Grabber.
+ * 
+ * In order to use the VFC you need to program the video controller
+ * chip. This chip is the Phillips SAA9051.  You need to call their
+ * documentation ordering line to get the docs.
+ *
+ * There is very little documentation on the VFC itself.  There is
+ * some useful info that can be found in the manuals that come with
+ * the card.  I will hopefully write some better docs at a later date.
+ *
+ * Copyright (C) 1996 Manish Vachharajani (mvachhar@noc.rutgers.edu)
+ * */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/fs.h>
+#include <linux/smp_lock.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sbus.h>
+#include <asm/page.h>
+#include <asm/pgtable.h>
+#include <asm/uaccess.h>
+
+#define VFC_MAJOR (60)
+
+#if 0
+#define VFC_IOCTL_DEBUG
+#endif
+
+#include "vfc.h"
+#include <asm/vfc_ioctls.h>
+
+static struct file_operations vfc_fops;
+struct vfc_dev **vfc_dev_lst;
+static char vfcstr[]="vfc";
+static unsigned char saa9051_init_array[VFC_SAA9051_NR] = {
+	0x00, 0x64, 0x72, 0x52,
+	0x36, 0x18, 0xff, 0x20,
+	0xfc, 0x77, 0xe3, 0x50,
+	0x3e
+};
+
+void vfc_lock_device(struct vfc_dev *dev)
+{
+	down(&dev->device_lock_sem);
+}
+
+void vfc_unlock_device(struct vfc_dev *dev)
+{
+	up(&dev->device_lock_sem);
+}
+
+
+void vfc_captstat_reset(struct vfc_dev *dev) 
+{
+	dev->control_reg |= VFC_CONTROL_CAPTRESET;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg &= ~VFC_CONTROL_CAPTRESET;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg |= VFC_CONTROL_CAPTRESET;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+}
+
+void vfc_memptr_reset(struct vfc_dev *dev) 
+{
+	dev->control_reg |= VFC_CONTROL_MEMPTR;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg &= ~VFC_CONTROL_MEMPTR;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg |= VFC_CONTROL_MEMPTR; 
+	sbus_writel(dev->control_reg, &dev->regs->control);
+}
+
+int vfc_csr_init(struct vfc_dev *dev)
+{
+	dev->control_reg = 0x80000000;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	udelay(200); 
+	dev->control_reg &= ~0x80000000;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	udelay(100); 
+	sbus_writel(0x0f000000, &dev->regs->i2c_magic2);
+
+	vfc_memptr_reset(dev);
+
+	dev->control_reg &= ~VFC_CONTROL_DIAGMODE;
+	dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+	dev->control_reg |= 0x40000000;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+
+	vfc_captstat_reset(dev);
+
+	return 0;
+}
+
+int vfc_saa9051_init(struct vfc_dev *dev)
+{
+	int i;
+
+	for (i = 0; i < VFC_SAA9051_NR; i++)
+		dev->saa9051_state_array[i] = saa9051_init_array[i];
+
+	vfc_i2c_sendbuf(dev,VFC_SAA9051_ADDR,
+			dev->saa9051_state_array, VFC_SAA9051_NR);
+	return 0;
+}
+
+int init_vfc_hw(struct vfc_dev *dev) 
+{
+	vfc_lock_device(dev);
+	vfc_csr_init(dev);
+
+	vfc_pcf8584_init(dev);
+	vfc_init_i2c_bus(dev); /* hopefully this doesn't undo the magic
+				  sun code above*/
+	vfc_saa9051_init(dev);
+	vfc_unlock_device(dev);
+	return 0; 
+}
+
+int init_vfc_devstruct(struct vfc_dev *dev, int instance) 
+{
+	dev->instance=instance;
+	init_MUTEX(&dev->device_lock_sem);
+	dev->control_reg=0;
+	init_waitqueue_head(&dev->poll_wait);
+	dev->busy=0;
+	return 0;
+}
+
+int init_vfc_device(struct sbus_dev *sdev,struct vfc_dev *dev, int instance)
+{
+	if(dev == NULL) {
+		printk(KERN_ERR "VFC: Bogus pointer passed\n");
+		return -ENOMEM;
+	}
+	printk("Initializing vfc%d\n",instance);
+	dev->regs = NULL;
+	dev->regs = (volatile struct vfc_regs *)
+		sbus_ioremap(&sdev->resource[0], 0,
+			     sizeof(struct vfc_regs), vfcstr);
+	dev->which_io = sdev->reg_addrs[0].which_io;
+	dev->phys_regs = (struct vfc_regs *) sdev->reg_addrs[0].phys_addr;
+	if (dev->regs == NULL)
+		return -EIO;
+
+	printk("vfc%d: registers mapped at phys_addr: 0x%lx\n    virt_addr: 0x%lx\n",
+	       instance,(unsigned long)sdev->reg_addrs[0].phys_addr,(unsigned long)dev->regs);
+
+	if (init_vfc_devstruct(dev, instance))
+		return -EINVAL;
+	if (init_vfc_hw(dev))
+		return -EIO;
+
+	devfs_mk_cdev(MKDEV(VFC_MAJOR, instance),
+			S_IFCHR | S_IRUSR | S_IWUSR,
+			"vfc/%d", instance);
+	return 0;
+}
+
+
+struct vfc_dev *vfc_get_dev_ptr(int instance) 
+{
+	return vfc_dev_lst[instance];
+}
+
+static DEFINE_SPINLOCK(vfc_dev_lock);
+
+static int vfc_open(struct inode *inode, struct file *file) 
+{
+	struct vfc_dev *dev;
+
+	spin_lock(&vfc_dev_lock);
+	dev = vfc_get_dev_ptr(iminor(inode));
+	if (dev == NULL) {
+		spin_unlock(&vfc_dev_lock);
+		return -ENODEV;
+	}
+	if (dev->busy) {
+		spin_unlock(&vfc_dev_lock);
+		return -EBUSY;
+	}
+
+	dev->busy = 1;
+	spin_unlock(&vfc_dev_lock);
+
+	vfc_lock_device(dev);
+	
+	vfc_csr_init(dev);
+	vfc_pcf8584_init(dev);
+	vfc_init_i2c_bus(dev);
+	vfc_saa9051_init(dev);
+	vfc_memptr_reset(dev);
+	vfc_captstat_reset(dev);
+	
+	vfc_unlock_device(dev);
+	return 0;
+}
+
+static int vfc_release(struct inode *inode,struct file *file) 
+{
+	struct vfc_dev *dev;
+
+	spin_lock(&vfc_dev_lock);
+	dev = vfc_get_dev_ptr(iminor(inode));
+	if (!dev || !dev->busy) {
+		spin_unlock(&vfc_dev_lock);
+		return -EINVAL;
+	}
+	dev->busy = 0;
+	spin_unlock(&vfc_dev_lock);
+	return 0;
+}
+
+static int vfc_debug(struct vfc_dev *dev, int cmd, void __user *argp)
+{
+	struct vfc_debug_inout inout;
+	unsigned char *buffer;
+
+	if (!capable(CAP_SYS_ADMIN))
+		return -EPERM;
+
+	switch(cmd) {
+	case VFC_I2C_SEND:
+		if(copy_from_user(&inout, argp, sizeof(inout)))
+			return -EFAULT;
+
+		buffer = kmalloc(inout.len, GFP_KERNEL);
+		if (buffer == NULL)
+			return -ENOMEM;
+
+		if(copy_from_user(buffer, inout.buffer, inout.len)) {
+			kfree(buffer);
+			return -EFAULT;
+		}
+		
+
+		vfc_lock_device(dev);
+		inout.ret=
+			vfc_i2c_sendbuf(dev,inout.addr & 0xff,
+					buffer,inout.len);
+
+		if (copy_to_user(argp,&inout,sizeof(inout))) {
+			kfree(buffer);
+			return -EFAULT;
+		}
+		vfc_unlock_device(dev);
+
+		break;
+	case VFC_I2C_RECV:
+		if (copy_from_user(&inout, argp, sizeof(inout)))
+			return -EFAULT;
+
+		buffer = kmalloc(inout.len, GFP_KERNEL);
+		if (buffer == NULL)
+			return -ENOMEM;
+
+		memset(buffer,0,inout.len);
+		vfc_lock_device(dev);
+		inout.ret=
+			vfc_i2c_recvbuf(dev,inout.addr & 0xff
+					,buffer,inout.len);
+		vfc_unlock_device(dev);
+		
+		if (copy_to_user(inout.buffer, buffer, inout.len)) {
+			kfree(buffer);
+			return -EFAULT;
+		}
+		if (copy_to_user(argp,&inout,sizeof(inout))) {
+			kfree(buffer);
+			return -EFAULT;
+		}
+		kfree(buffer);
+		break;
+	default:
+		return -EINVAL;
+	};
+
+	return 0;
+}
+
+int vfc_capture_start(struct vfc_dev *dev) 
+{
+	vfc_captstat_reset(dev);
+	dev->control_reg = sbus_readl(&dev->regs->control);
+	if((dev->control_reg & VFC_STATUS_CAPTURE)) {
+		printk(KERN_ERR "vfc%d: vfc capture status not reset\n",
+		       dev->instance);
+		return -EIO;
+	}
+
+	vfc_lock_device(dev);
+	dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg |= VFC_CONTROL_CAPTURE;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	dev->control_reg &= ~VFC_CONTROL_CAPTURE;
+	sbus_writel(dev->control_reg, &dev->regs->control);
+	vfc_unlock_device(dev);
+
+	return 0;
+}
+
+int vfc_capture_poll(struct vfc_dev *dev) 
+{
+	int timeout = 1000;
+
+	while (!timeout--) {
+		if (dev->regs->control & VFC_STATUS_CAPTURE)
+			break;
+		vfc_i2c_delay_no_busy(dev, 100);
+	}
+	if(!timeout) {
+		printk(KERN_WARNING "vfc%d: capture timed out\n",
+		       dev->instance);
+		return -ETIMEDOUT;
+	}
+	return 0;
+}
+
+
+
+static int vfc_set_control_ioctl(struct inode *inode, struct file *file, 
+			  struct vfc_dev *dev, unsigned long arg) 
+{
+	int setcmd, ret = 0;
+
+	if (copy_from_user(&setcmd,(void __user *)arg,sizeof(unsigned int)))
+		return -EFAULT;
+
+	VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCSCTRL) arg=0x%x\n",
+				dev->instance,setcmd));
+
+	switch(setcmd) {
+	case MEMPRST:
+		vfc_lock_device(dev);
+		vfc_memptr_reset(dev);
+		vfc_unlock_device(dev);
+		ret=0;
+		break;
+	case CAPTRCMD:
+		vfc_capture_start(dev);
+		vfc_capture_poll(dev);
+		break;
+	case DIAGMODE:
+		if(capable(CAP_SYS_ADMIN)) {
+			vfc_lock_device(dev);
+			dev->control_reg |= VFC_CONTROL_DIAGMODE;
+			sbus_writel(dev->control_reg, &dev->regs->control);
+			vfc_unlock_device(dev);
+			ret = 0;
+		} else {
+			ret = -EPERM; 
+		}
+		break;
+	case NORMMODE:
+		vfc_lock_device(dev);
+		dev->control_reg &= ~VFC_CONTROL_DIAGMODE;
+		sbus_writel(dev->control_reg, &dev->regs->control);
+		vfc_unlock_device(dev);
+		ret = 0;
+		break;
+	case CAPTRSTR:
+		vfc_capture_start(dev);
+		ret = 0;
+		break;
+	case CAPTRWAIT:
+		vfc_capture_poll(dev);
+		ret = 0;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	};
+
+	return ret;
+}
+
+
+int vfc_port_change_ioctl(struct inode *inode, struct file *file, 
+			  struct vfc_dev *dev, unsigned long arg) 
+{
+	int ret = 0;
+	int cmd;
+
+	if(copy_from_user(&cmd, (void __user *)arg, sizeof(unsigned int))) {
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+					"vfc_port_change_ioctl\n",
+					dev->instance));
+		return -EFAULT;
+	}
+	
+	VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCPORTCHG) arg=0x%x\n",
+				dev->instance, cmd));
+
+	switch(cmd) {
+	case 1:
+	case 2:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_START) = 0x72; 
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_STOP) = 0x52;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HC_START) = 0x36;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HC_STOP) = 0x18;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) = VFC_SAA9051_BP2;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C3) = VFC_SAA9051_CT | VFC_SAA9051_SS3;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_SECAM_DELAY) = 0x3e;
+		break;
+	case 3:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_START) = 0x3a;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HSY_STOP) = 0x17;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HC_START) = 0xfa;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HC_STOP) = 0xde;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) =
+			VFC_SAA9051_BY | VFC_SAA9051_PF | VFC_SAA9051_BP2;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C3) = VFC_SAA9051_YC;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_SECAM_DELAY) = 0;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C2) &=
+			~(VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+		break;
+	default:
+		ret = -EINVAL;
+		return ret;
+		break;
+	}
+
+	switch(cmd) {
+	case 1:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C2) |=
+			(VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+		break;
+	case 2:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C2) &=
+			~(VFC_SAA9051_SS0 | VFC_SAA9051_SS1);
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C2) |= VFC_SAA9051_SS0; 
+		break;
+	case 3:
+		break;
+	default:
+		ret = -EINVAL;
+		return ret;
+		break;
+	}
+	VFC_SAA9051_SA(dev,VFC_SAA9051_C3) &= ~(VFC_SAA9051_SS2);
+	ret=vfc_update_saa9051(dev);
+	udelay(500);
+	VFC_SAA9051_SA(dev,VFC_SAA9051_C3) |= (VFC_SAA9051_SS2);
+	ret=vfc_update_saa9051(dev);
+	return ret;
+}
+
+int vfc_set_video_ioctl(struct inode *inode, struct file *file, 
+			struct vfc_dev *dev, unsigned long arg) 
+{
+	int ret = 0;
+	int cmd;
+
+	if(copy_from_user(&cmd, (void __user *)arg, sizeof(unsigned int))) {
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+					"vfc_set_video_ioctl\n",
+					dev->instance));
+		return ret;
+	}
+	
+	VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCSVID) arg=0x%x\n",
+				dev->instance, cmd));
+	switch(cmd) {
+	case STD_NTSC:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~VFC_SAA9051_ALT;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_YPN | 
+			VFC_SAA9051_CCFR0 | VFC_SAA9051_CCFR1 | VFC_SAA9051_FS;
+		ret = vfc_update_saa9051(dev);
+		break;
+	case STD_PAL:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~(VFC_SAA9051_YPN | 
+							VFC_SAA9051_CCFR1 | 
+							VFC_SAA9051_CCFR0 |
+							VFC_SAA9051_FS);
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_ALT;
+		ret = vfc_update_saa9051(dev);
+		break;
+
+	case COLOR_ON:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) |= VFC_SAA9051_CO;
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) &=
+			~(VFC_SAA9051_BY | VFC_SAA9051_PF);
+		ret = vfc_update_saa9051(dev);
+		break;
+	case MONO:
+		VFC_SAA9051_SA(dev,VFC_SAA9051_C1) &= ~(VFC_SAA9051_CO);
+		VFC_SAA9051_SA(dev,VFC_SAA9051_HORIZ_PEAK) |=
+			(VFC_SAA9051_BY | VFC_SAA9051_PF);
+		ret = vfc_update_saa9051(dev);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	};
+
+	return ret;
+}
+
+int vfc_get_video_ioctl(struct inode *inode, struct file *file, 
+			struct vfc_dev *dev, unsigned long arg) 
+{
+	int ret = 0;
+	unsigned int status = NO_LOCK;
+	unsigned char buf[1];
+
+	if(vfc_i2c_recvbuf(dev, VFC_SAA9051_ADDR, buf, 1)) {
+		printk(KERN_ERR "vfc%d: Unable to get status\n",
+		       dev->instance);
+		return -EIO;
+	}
+
+	if(buf[0] & VFC_SAA9051_HLOCK) {
+		status = NO_LOCK;
+	} else if(buf[0] & VFC_SAA9051_FD) {
+		if(buf[0] & VFC_SAA9051_CD)
+			status = NTSC_COLOR;
+		else
+			status = NTSC_NOCOLOR;
+	} else {
+		if(buf[0] & VFC_SAA9051_CD)
+			status = PAL_COLOR;
+		else
+			status = PAL_NOCOLOR;
+	}
+	VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCGVID) returning status 0x%x; "
+				"buf[0]=%x\n", dev->instance, status, buf[0]));
+
+	if (copy_to_user((void __user *)arg,&status,sizeof(unsigned int))) {
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer to "
+					"vfc_get_video_ioctl\n",
+					dev->instance));
+		return ret;
+	}
+	return ret;
+}
+
+static int vfc_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
+	      unsigned long arg) 
+{
+	int ret = 0;
+	unsigned int tmp;
+	struct vfc_dev *dev;
+	void __user *argp = (void __user *)arg;
+
+	dev = vfc_get_dev_ptr(iminor(inode));
+	if(dev == NULL)
+		return -ENODEV;
+	
+	switch(cmd & 0x0000ffff) {
+	case VFCGCTRL:
+#if 0
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCGCTRL)\n", dev->instance));
+#endif
+		tmp = sbus_readl(&dev->regs->control);
+		if(copy_to_user(argp, &tmp, sizeof(unsigned int))) {
+			ret = -EFAULT;
+			break;
+		}
+		ret = 0;
+		break;
+	case VFCSCTRL:
+		ret = vfc_set_control_ioctl(inode, file, dev, arg);
+		break;
+	case VFCGVID:
+		ret = vfc_get_video_ioctl(inode, file, dev, arg);
+		break;
+	case VFCSVID:
+		ret = vfc_set_video_ioctl(inode, file, dev, arg);
+		break;
+	case VFCHUE:
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCHUE)\n", dev->instance));
+		if(copy_from_user(&tmp,argp,sizeof(unsigned int))) {
+			VFC_IOCTL_DEBUG_PRINTK(("vfc%d: User passed bogus pointer "
+						"to IOCTL(VFCHUE)", dev->instance));
+			ret = -EFAULT;
+		} else {
+			VFC_SAA9051_SA(dev,VFC_SAA9051_HUE) = tmp;
+			vfc_update_saa9051(dev);
+			ret = 0;
+		}
+		break;
+	case VFCPORTCHG:
+		ret = vfc_port_change_ioctl(inode, file, dev, arg);
+		break;
+	case VFCRDINFO:
+		ret = -EINVAL;
+		VFC_IOCTL_DEBUG_PRINTK(("vfc%d: IOCTL(VFCRDINFO)\n", dev->instance));
+		break;
+	default:
+		ret = vfc_debug(vfc_get_dev_ptr(iminor(inode)), cmd, argp);
+		break;
+	};
+
+	return ret;
+}
+
+static int vfc_mmap(struct file *file, struct vm_area_struct *vma) 
+{
+	unsigned int map_size, ret, map_offset;
+	struct vfc_dev *dev;
+	
+	dev = vfc_get_dev_ptr(iminor(file->f_dentry->d_inode));
+	if(dev == NULL)
+		return -ENODEV;
+
+	map_size = vma->vm_end - vma->vm_start;
+	if(map_size > sizeof(struct vfc_regs)) 
+		map_size = sizeof(struct vfc_regs);
+
+	vma->vm_flags |=
+		(VM_SHM | VM_LOCKED | VM_IO | VM_MAYREAD | VM_MAYWRITE | VM_MAYSHARE);
+	map_offset = (unsigned int) (long)dev->phys_regs;
+	ret = io_remap_pfn_range(vma, vma->vm_start,
+				  MK_IOSPACE_PFN(dev->which_io,
+					map_offset >> PAGE_SHIFT),
+				  map_size, vma->vm_page_prot);
+
+	if(ret)
+		return -EAGAIN;
+
+	return 0;
+}
+
+
+static struct file_operations vfc_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.ioctl =	vfc_ioctl,
+	.mmap =		vfc_mmap,
+	.open =		vfc_open,
+	.release =	vfc_release,
+};
+
+static int vfc_probe(void)
+{
+	struct sbus_bus *sbus;
+	struct sbus_dev *sdev = NULL;
+	int ret;
+	int instance = 0, cards = 0;
+
+	for_all_sbusdev(sdev, sbus) {
+		if (strcmp(sdev->prom_name, "vfc") == 0) {
+			cards++;
+			continue;
+		}
+	}
+
+	if (!cards)
+		return -ENODEV;
+
+	vfc_dev_lst = (struct vfc_dev **)kmalloc(sizeof(struct vfc_dev *) *
+						 (cards+1),
+						 GFP_KERNEL);
+	if (vfc_dev_lst == NULL)
+		return -ENOMEM;
+	memset(vfc_dev_lst, 0, sizeof(struct vfc_dev *) * (cards + 1));
+	vfc_dev_lst[cards] = NULL;
+
+	ret = register_chrdev(VFC_MAJOR, vfcstr, &vfc_fops);
+	if(ret) {
+		printk(KERN_ERR "Unable to get major number %d\n", VFC_MAJOR);
+		kfree(vfc_dev_lst);
+		return -EIO;
+	}
+	devfs_mk_dir("vfc");
+	instance = 0;
+	for_all_sbusdev(sdev, sbus) {
+		if (strcmp(sdev->prom_name, "vfc") == 0) {
+			vfc_dev_lst[instance]=(struct vfc_dev *)
+				kmalloc(sizeof(struct vfc_dev), GFP_KERNEL);
+			if (vfc_dev_lst[instance] == NULL)
+				return -ENOMEM;
+			ret = init_vfc_device(sdev,
+					      vfc_dev_lst[instance],
+					      instance);
+			if(ret) {
+				printk(KERN_ERR "Unable to initialize"
+				       " vfc%d device\n",
+				       instance);
+			} else {
+			}
+		
+			instance++;
+			continue;
+		}
+	}
+
+	return 0;
+}
+
+#ifdef MODULE
+int init_module(void)
+#else 
+int vfc_init(void)
+#endif
+{
+	return vfc_probe();
+}
+
+#ifdef MODULE
+static void deinit_vfc_device(struct vfc_dev *dev)
+{
+	if(dev == NULL)
+		return;
+	devfs_remove("vfc/%d", dev->instance);
+	sbus_iounmap((unsigned long)dev->regs, sizeof(struct vfc_regs));
+	kfree(dev);
+}
+
+void cleanup_module(void)
+{
+	struct vfc_dev **devp;
+
+	unregister_chrdev(VFC_MAJOR,vfcstr);
+
+	for (devp = vfc_dev_lst; *devp; devp++)
+		deinit_vfc_device(*devp);
+
+	devfs_remove("vfc");
+	kfree(vfc_dev_lst);
+	return;
+}
+#endif
+
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/sbus/char/vfc_i2c.c b/drivers/sbus/char/vfc_i2c.c
new file mode 100644
index 0000000..95e3ceb
--- /dev/null
+++ b/drivers/sbus/char/vfc_i2c.c
@@ -0,0 +1,347 @@
+/*
+ * drivers/sbus/char/vfc_i2c.c
+ *
+ * Driver for the Videopix Frame Grabber.
+ * 
+ * Functions that support the Phillips i2c(I squared C) bus on the vfc
+ *  Documentation for the Phillips I2C bus can be found on the 
+ *  phillips home page
+ *
+ * Copyright (C) 1996 Manish Vachharajani (mvachhar@noc.rutgers.edu)
+ *
+ */
+
+/* NOTE: It seems to me that the documentation regarding the
+pcd8584t/pcf8584 does not show the correct way to address the i2c bus.
+Based on the information on the I2C bus itself and the remainder of
+the Phillips docs the following algorithims apper to be correct.  I am
+fairly certain that the flowcharts in the phillips docs are wrong. */
+
+
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/system.h>
+#include <asm/sbus.h>
+
+#if 0 
+#define VFC_I2C_DEBUG
+#endif
+
+#include "vfc.h"
+#include "vfc_i2c.h"
+
+#define WRITE_S1(__val) \
+	sbus_writel(__val, &dev->regs->i2c_s1)
+#define WRITE_REG(__val) \
+	sbus_writel(__val, &dev->regs->i2c_reg)
+
+#define VFC_I2C_READ (0x1)
+#define VFC_I2C_WRITE (0x0)
+     
+/****** 
+  The i2c bus controller chip on the VFC is a pcd8584t, but
+  phillips claims it doesn't exist.  As far as I can tell it is
+  identical to the PCF8584 so I treat it like it is the pcf8584.
+  
+  NOTE: The pcf8584 only cares
+  about the msb of the word you feed it 
+*****/
+
+int vfc_pcf8584_init(struct vfc_dev *dev) 
+{
+	/* This will also choose register S0_OWN so we can set it. */
+	WRITE_S1(RESET);
+
+	/* The pcf8584 shifts this value left one bit and uses
+	 * it as its i2c bus address.
+	 */
+	WRITE_REG(0x55000000);
+
+	/* This will set the i2c bus at the same speed sun uses,
+	 * and set another magic bit.
+	 */
+	WRITE_S1(SELECT(S2));
+	WRITE_REG(0x14000000);
+	
+	/* Enable the serial port, idle the i2c bus and set
+	 * the data reg to s0.
+	 */
+	WRITE_S1(CLEAR_I2C_BUS);
+	udelay(100);
+	return 0;
+}
+
+void vfc_i2c_delay_wakeup(struct vfc_dev *dev) 
+{
+	/* Used to profile code and eliminate too many delays */
+	VFC_I2C_DEBUG_PRINTK(("vfc%d: Delaying\n", dev->instance));
+	wake_up(&dev->poll_wait);
+}
+
+void vfc_i2c_delay_no_busy(struct vfc_dev *dev, unsigned long usecs) 
+{
+	init_timer(&dev->poll_timer);
+	dev->poll_timer.expires = jiffies + 
+		((unsigned long)usecs*(HZ))/1000000;
+	dev->poll_timer.data=(unsigned long)dev;
+	dev->poll_timer.function=(void *)(unsigned long)vfc_i2c_delay_wakeup;
+	add_timer(&dev->poll_timer);
+	sleep_on(&dev->poll_wait);
+	del_timer(&dev->poll_timer);
+}
+
+void inline vfc_i2c_delay(struct vfc_dev *dev) 
+{ 
+	vfc_i2c_delay_no_busy(dev, 100);
+}
+
+int vfc_init_i2c_bus(struct vfc_dev *dev)
+{
+	WRITE_S1(ENABLE_SERIAL | SELECT(S0) | ACK);
+	vfc_i2c_reset_bus(dev);
+	return 0;
+}
+
+int vfc_i2c_reset_bus(struct vfc_dev *dev) 
+{
+	VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: Resetting the i2c bus\n",
+			      dev->instance));
+	if(dev == NULL)
+		return -EINVAL;
+	if(dev->regs == NULL)
+		return -EINVAL;
+	WRITE_S1(SEND_I2C_STOP);
+	WRITE_S1(SEND_I2C_STOP | ACK);
+	vfc_i2c_delay(dev);
+	WRITE_S1(CLEAR_I2C_BUS);
+	VFC_I2C_DEBUG_PRINTK((KERN_DEBUG "vfc%d: I2C status %x\n",
+			      dev->instance,
+			      sbus_readl(&dev->regs->i2c_s1)));
+	return 0;
+}
+
+int vfc_i2c_wait_for_bus(struct vfc_dev *dev) 
+{
+	int timeout = 1000; 
+
+	while(!(sbus_readl(&dev->regs->i2c_s1) & BB)) {
+		if(!(timeout--))
+			return -ETIMEDOUT;
+		vfc_i2c_delay(dev);
+	}
+	return 0;
+}
+
+int vfc_i2c_wait_for_pin(struct vfc_dev *dev, int ack)
+{
+	int timeout = 1000; 
+	int s1;
+
+	while ((s1 = sbus_readl(&dev->regs->i2c_s1)) & PIN) {
+		if (!(timeout--))
+			return -ETIMEDOUT;
+		vfc_i2c_delay(dev);
+	}
+	if (ack == VFC_I2C_ACK_CHECK) {
+		if(s1 & LRB)
+			return -EIO; 
+	}
+	return 0;
+}
+
+#define SHIFT(a) ((a) << 24)
+int vfc_i2c_xmit_addr(struct vfc_dev *dev, unsigned char addr, char mode) 
+{ 
+	int ret, raddr;
+#if 1
+	WRITE_S1(SEND_I2C_STOP | ACK);
+	WRITE_S1(SELECT(S0) | ENABLE_SERIAL);
+	vfc_i2c_delay(dev);
+#endif
+
+	switch(mode) {
+	case VFC_I2C_READ:
+		raddr = SHIFT(((unsigned int)addr | 0x1));
+		WRITE_REG(raddr);
+		VFC_I2C_DEBUG_PRINTK(("vfc%d: receiving from i2c addr 0x%x\n",
+				      dev->instance, addr | 0x1));
+		break;
+	case VFC_I2C_WRITE:
+		raddr = SHIFT((unsigned int)addr & ~0x1);
+		WRITE_REG(raddr);
+		VFC_I2C_DEBUG_PRINTK(("vfc%d: sending to i2c addr 0x%x\n",
+				      dev->instance, addr & ~0x1));
+		break;
+	default:
+		return -EINVAL;
+	};
+
+	WRITE_S1(SEND_I2C_START);
+	vfc_i2c_delay(dev);
+	ret = vfc_i2c_wait_for_pin(dev,VFC_I2C_ACK_CHECK); /* We wait
+							      for the
+							      i2c send
+							      to finish
+							      here but
+							      Sun
+							      doesn't,
+							      hmm */
+	if (ret) {
+		printk(KERN_ERR "vfc%d: VFC xmit addr timed out or no ack\n",
+		       dev->instance);
+		return ret;
+	} else if (mode == VFC_I2C_READ) {
+		if ((ret = sbus_readl(&dev->regs->i2c_reg) & 0xff000000) != raddr) {
+			printk(KERN_WARNING 
+			       "vfc%d: returned slave address "
+			       "mismatch(%x,%x)\n",
+			       dev->instance, raddr, ret);
+		}
+	}	
+	return 0;
+}
+
+int vfc_i2c_xmit_byte(struct vfc_dev *dev,unsigned char *byte) 
+{
+	int ret;
+	u32 val = SHIFT((unsigned int)*byte);
+
+	WRITE_REG(val);
+
+	ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_ACK_CHECK); 
+	switch(ret) {
+	case -ETIMEDOUT: 
+		printk(KERN_ERR "vfc%d: VFC xmit byte timed out or no ack\n",
+		       dev->instance);
+		break;
+	case -EIO:
+		ret = XMIT_LAST_BYTE;
+		break;
+	default:
+		break;
+	};
+
+	return ret;
+}
+
+int vfc_i2c_recv_byte(struct vfc_dev *dev, unsigned char *byte, int last) 
+{
+	int ret;
+
+	if (last) {
+		WRITE_REG(NEGATIVE_ACK);
+		VFC_I2C_DEBUG_PRINTK(("vfc%d: sending negative ack\n",
+				      dev->instance));
+	} else {
+		WRITE_S1(ACK);
+	}
+
+	ret = vfc_i2c_wait_for_pin(dev, VFC_I2C_NO_ACK_CHECK);
+	if(ret) {
+		printk(KERN_ERR "vfc%d: "
+		       "VFC recv byte timed out\n",
+		       dev->instance);
+	}
+	*byte = (sbus_readl(&dev->regs->i2c_reg)) >> 24;
+	return ret;
+}
+
+int vfc_i2c_recvbuf(struct vfc_dev *dev, unsigned char addr,
+		    char *buf, int count)
+{
+	int ret, last;
+
+	if(!(count && buf && dev && dev->regs) )
+		return -EINVAL;
+
+	if ((ret = vfc_i2c_wait_for_bus(dev))) {
+		printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance);
+		return ret;
+	}
+
+	if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_READ))) {
+		WRITE_S1(SEND_I2C_STOP);
+		vfc_i2c_delay(dev);
+		return ret;
+	}
+	
+	last = 0;
+	while (count--) {
+		if (!count)
+			last = 1;
+		if ((ret = vfc_i2c_recv_byte(dev, buf, last))) {
+			printk(KERN_ERR "vfc%d: "
+			       "VFC error while receiving byte\n",
+			       dev->instance);
+			WRITE_S1(SEND_I2C_STOP);
+			ret = -EINVAL;
+		}
+		buf++;
+	}
+	WRITE_S1(SEND_I2C_STOP | ACK);
+	vfc_i2c_delay(dev);
+	return ret;
+}
+
+int vfc_i2c_sendbuf(struct vfc_dev *dev, unsigned char addr, 
+		    char *buf, int count) 
+{
+	int ret;
+	
+	if (!(buf && dev && dev->regs))
+		return -EINVAL;
+	
+	if ((ret = vfc_i2c_wait_for_bus(dev))) {
+		printk(KERN_ERR "vfc%d: VFC I2C bus busy\n", dev->instance);
+		return ret;
+	}
+	
+	if ((ret = vfc_i2c_xmit_addr(dev, addr, VFC_I2C_WRITE))) {
+		WRITE_S1(SEND_I2C_STOP);
+		vfc_i2c_delay(dev);
+		return ret;
+	}
+	
+	while(count--) {
+		ret = vfc_i2c_xmit_byte(dev, buf);
+		switch(ret) {
+		case XMIT_LAST_BYTE:
+			VFC_I2C_DEBUG_PRINTK(("vfc%d: "
+					      "Receiver ended transmission with "
+					      " %d bytes remaining\n",
+					      dev->instance, count));
+			ret = 0;
+			goto done;
+			break;
+		case 0:
+			break;
+		default:
+			printk(KERN_ERR "vfc%d: "
+			       "VFC error while sending byte\n", dev->instance);
+			break;
+		};
+
+		buf++;
+	}
+done:
+	WRITE_S1(SEND_I2C_STOP | ACK);
+	vfc_i2c_delay(dev);
+	return ret;
+}
+
+
+
+
+
+
+
+
+
diff --git a/drivers/sbus/char/vfc_i2c.h b/drivers/sbus/char/vfc_i2c.h
new file mode 100644
index 0000000..a2e6973
--- /dev/null
+++ b/drivers/sbus/char/vfc_i2c.h
@@ -0,0 +1,44 @@
+#ifndef _LINUX_VFC_I2C_H_
+#define _LINUX_VFC_I2C_H_
+
+/* control bits */
+#define PIN  (0x80000000)
+#define ESO  (0x40000000)
+#define ES1  (0x20000000)
+#define ES2  (0x10000000)
+#define ENI  (0x08000000)
+#define STA  (0x04000000)
+#define STO  (0x02000000)
+#define ACK  (0x01000000)
+
+/* status bits */
+#define STS  (0x20000000)
+#define BER  (0x10000000)
+#define LRB  (0x08000000)
+#define AAS  (0x04000000)
+#define LAB  (0x02000000)
+#define BB   (0x01000000)
+
+#define SEND_I2C_START (PIN | ESO | STA)
+#define SEND_I2C_STOP (PIN | ESO | STO)
+#define CLEAR_I2C_BUS (PIN | ESO | ACK)
+#define NEGATIVE_ACK ((ESO) & ~ACK)
+
+#define SELECT(a) (a)
+#define S0 (PIN | ESO | ES1)
+#define S0_OWN (PIN)
+#define S2 (PIN | ES1)
+#define S3 (PIN | ES2)
+
+#define ENABLE_SERIAL (PIN | ESO)
+#define DISABLE_SERIAL (PIN)
+#define RESET (PIN)
+
+#define XMIT_LAST_BYTE (1)
+#define VFC_I2C_ACK_CHECK (1)
+#define VFC_I2C_NO_ACK_CHECK (0)
+
+#endif /* _LINUX_VFC_I2C_H_ */
+
+
+
diff --git a/drivers/sbus/dvma.c b/drivers/sbus/dvma.c
new file mode 100644
index 0000000..378a1d6
--- /dev/null
+++ b/drivers/sbus/dvma.c
@@ -0,0 +1,137 @@
+/* dvma.c:  Routines that are used to access DMA on the Sparc SBus.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+#include <linux/config.h>
+#include <linux/string.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <asm/oplib.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <asm/sbus.h>
+
+struct sbus_dma *dma_chain;
+
+void __init init_one_dvma(struct sbus_dma *dma, int num_dma)
+{
+	printk("dma%d: ", num_dma);
+	
+	dma->next = NULL;
+	dma->running = 0;      /* No transfers going on as of yet */
+	dma->allocated = 0;    /* No one has allocated us yet */
+	switch(sbus_readl(dma->regs + DMA_CSR)&DMA_DEVICE_ID) {
+	case DMA_VERS0:
+		dma->revision = dvmarev0;
+		printk("Revision 0 ");
+		break;
+	case DMA_ESCV1:
+		dma->revision = dvmaesc1;
+		printk("ESC Revision 1 ");
+		break;
+	case DMA_VERS1:
+		dma->revision = dvmarev1;
+		printk("Revision 1 ");
+		break;
+	case DMA_VERS2:
+		dma->revision = dvmarev2;
+		printk("Revision 2 ");
+		break;
+	case DMA_VERHME:
+		dma->revision = dvmahme;
+		printk("HME DVMA gate array ");
+		break;
+	case DMA_VERSPLUS:
+		dma->revision = dvmarevplus;
+		printk("Revision 1 PLUS ");
+		break;
+	default:
+		printk("unknown dma version %08x",
+		       sbus_readl(dma->regs + DMA_CSR) & DMA_DEVICE_ID);
+		dma->allocated = 1;
+		break;
+	}
+	printk("\n");
+}
+
+/* Probe this SBus DMA module(s) */
+void __init dvma_init(struct sbus_bus *sbus)
+{
+	struct sbus_dev *this_dev;
+	struct sbus_dma *dma;
+	struct sbus_dma *dchain;
+	static int num_dma = 0;
+
+	for_each_sbusdev(this_dev, sbus) {
+		char *name = this_dev->prom_name;
+		int hme = 0;
+
+		if(!strcmp(name, "SUNW,fas"))
+			hme = 1;
+		else if(strcmp(name, "dma") &&
+			strcmp(name, "ledma") &&
+			strcmp(name, "espdma"))
+			continue;
+
+		/* Found one... */
+		dma = kmalloc(sizeof(struct sbus_dma), GFP_ATOMIC);
+
+		dma->sdev = this_dev;
+
+		/* Put at end of dma chain */
+		dchain = dma_chain;
+		if(dchain) {
+			while(dchain->next)
+				dchain = dchain->next;
+			dchain->next = dma;
+		} else {
+			/* We're the first in line */
+			dma_chain = dma;
+		}
+
+		dma->regs = sbus_ioremap(&dma->sdev->resource[0], 0,
+					 dma->sdev->resource[0].end - dma->sdev->resource[0].start + 1,
+					 "dma");
+
+		dma->node = dma->sdev->prom_node;
+		
+		init_one_dvma(dma, num_dma++);
+	}
+}
+
+#ifdef CONFIG_SUN4
+
+#include <asm/sun4paddr.h>
+
+void __init sun4_dvma_init(void)
+{
+	struct sbus_dma *dma;
+	struct resource r;
+
+	if(sun4_dma_physaddr) {
+		dma = kmalloc(sizeof(struct sbus_dma), GFP_ATOMIC);
+
+		/* No SBUS */
+		dma->sdev = NULL;
+
+		/* Only one DMA device */
+		dma_chain = dma;
+
+		memset(&r, 0, sizeof(r));
+		r.start = sun4_dma_physaddr;
+		dma->regs = sbus_ioremap(&r, 0, PAGE_SIZE, "dma");
+
+		/* No prom node */
+		dma->node = 0x0;
+
+		init_one_dvma(dma, 0);
+	} else {
+	  	dma_chain = NULL;
+	}
+}
+
+#endif
diff --git a/drivers/sbus/sbus.c b/drivers/sbus/sbus.c
new file mode 100644
index 0000000..5d30a3e
--- /dev/null
+++ b/drivers/sbus/sbus.c
@@ -0,0 +1,564 @@
+/* $Id: sbus.c,v 1.100 2002/01/24 15:36:24 davem Exp $
+ * sbus.c:  SBus support routines.
+ *
+ * Copyright (C) 1995 David S. Miller (davem@caip.rutgers.edu)
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/config.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+
+#include <asm/system.h>
+#include <asm/sbus.h>
+#include <asm/dma.h>
+#include <asm/oplib.h>
+#include <asm/bpp.h>
+#include <asm/irq.h>
+
+struct sbus_bus *sbus_root = NULL;
+
+static struct linux_prom_irqs irqs[PROMINTR_MAX] __initdata = { { 0 } };
+#ifdef CONFIG_SPARC32
+static int interrupts[PROMINTR_MAX] __initdata = { 0 };
+#endif
+
+#ifdef CONFIG_PCI
+extern int pcic_present(void);
+#endif
+
+/* Perhaps when I figure out more about the iommu we'll put a
+ * device registration routine here that probe_sbus() calls to
+ * setup the iommu for each Sbus.
+ */
+
+/* We call this for each SBus device, and fill the structure based
+ * upon the prom device tree.  We return the start of memory after
+ * the things we have allocated.
+ */
+
+/* #define DEBUG_FILL */
+
+static void __init fill_sbus_device(int prom_node, struct sbus_dev *sdev)
+{
+	unsigned long address, base;
+	int len;
+
+	sdev->prom_node = prom_node;
+	prom_getstring(prom_node, "name",
+		       sdev->prom_name, sizeof(sdev->prom_name));
+	address = prom_getint(prom_node, "address");
+	len = prom_getproperty(prom_node, "reg",
+			       (char *) sdev->reg_addrs,
+			       sizeof(sdev->reg_addrs));
+	if (len == -1) {
+		sdev->num_registers = 0;
+		goto no_regs;
+	}
+
+	if (len % sizeof(struct linux_prom_registers)) {
+		prom_printf("fill_sbus_device: proplen for regs of %s "
+			    " was %d, need multiple of %d\n",
+			    sdev->prom_name, len,
+			    (int) sizeof(struct linux_prom_registers));
+		prom_halt();
+	}
+	if (len > (sizeof(struct linux_prom_registers) * PROMREG_MAX)) {
+		prom_printf("fill_sbus_device: Too many register properties "
+			    "for device %s, len=%d\n",
+			    sdev->prom_name, len);
+		prom_halt();
+	}
+	sdev->num_registers = len / sizeof(struct linux_prom_registers);
+	sdev->ranges_applied = 0;
+
+	base = (unsigned long) sdev->reg_addrs[0].phys_addr;
+
+	/* Compute the slot number. */
+	if (base >= SUN_SBUS_BVADDR && sparc_cpu_model == sun4m) {
+		sdev->slot = sbus_dev_slot(base);
+	} else {
+		sdev->slot = sdev->reg_addrs[0].which_io;
+	}
+
+no_regs:
+	len = prom_getproperty(prom_node, "ranges",
+			       (char *)sdev->device_ranges,
+			       sizeof(sdev->device_ranges));
+	if (len == -1) {
+		sdev->num_device_ranges = 0;
+		goto no_ranges;
+	}
+	if (len % sizeof(struct linux_prom_ranges)) {
+		prom_printf("fill_sbus_device: proplen for ranges of %s "
+			    " was %d, need multiple of %d\n",
+			    sdev->prom_name, len,
+			    (int) sizeof(struct linux_prom_ranges));
+		prom_halt();
+	}
+	if (len > (sizeof(struct linux_prom_ranges) * PROMREG_MAX)) {
+		prom_printf("fill_sbus_device: Too many range properties "
+			    "for device %s, len=%d\n",
+			    sdev->prom_name, len);
+		prom_halt();
+	}
+	sdev->num_device_ranges =
+		len / sizeof(struct linux_prom_ranges);
+
+no_ranges:
+	/* XXX Unfortunately, IRQ issues are very arch specific.
+	 * XXX Pull this crud out into an arch specific area
+	 * XXX at some point. -DaveM
+	 */
+#ifdef CONFIG_SPARC64
+	len = prom_getproperty(prom_node, "interrupts",
+			       (char *) irqs, sizeof(irqs));
+	if (len == -1 || len == 0) {
+		sdev->irqs[0] = 0;
+		sdev->num_irqs = 0;
+	} else {
+		unsigned int pri = irqs[0].pri;
+
+		sdev->num_irqs = 1;
+		if (pri < 0x20)
+			pri += sdev->slot * 8;
+
+		sdev->irqs[0] =	sbus_build_irq(sdev->bus, pri);
+	}
+#endif /* CONFIG_SPARC64 */
+
+#ifdef CONFIG_SPARC32
+	len = prom_getproperty(prom_node, "intr",
+			       (char *)irqs, sizeof(irqs));
+	if (len != -1) {
+		sdev->num_irqs = len / 8;
+		if (sdev->num_irqs == 0) {
+			sdev->irqs[0] = 0;
+		} else if (sparc_cpu_model == sun4d) {
+			extern unsigned int sun4d_build_irq(struct sbus_dev *sdev, int irq);
+
+			for (len = 0; len < sdev->num_irqs; len++)
+				sdev->irqs[len] = sun4d_build_irq(sdev, irqs[len].pri);
+		} else {
+			for (len = 0; len < sdev->num_irqs; len++)
+				sdev->irqs[len] = irqs[len].pri;
+		}
+	} else {
+		/* No "intr" node found-- check for "interrupts" node.
+		 * This node contains SBus interrupt levels, not IPLs
+		 * as in "intr", and no vector values.  We convert 
+		 * SBus interrupt levels to PILs (platform specific).
+		 */
+		len = prom_getproperty(prom_node, "interrupts", 
+					(char *)interrupts, sizeof(interrupts));
+		if (len == -1) {
+			sdev->irqs[0] = 0;
+			sdev->num_irqs = 0;
+		} else {
+			sdev->num_irqs = len / sizeof(int);
+			for (len = 0; len < sdev->num_irqs; len++) {
+				sdev->irqs[len] = sbint_to_irq(sdev, interrupts[len]);
+			}
+		}
+	} 
+#endif /* CONFIG_SPARC32 */
+}
+
+/* This routine gets called from whoever needs the sbus first, to scan
+ * the SBus device tree.  Currently it just prints out the devices
+ * found on the bus and builds trees of SBUS structs and attached
+ * devices.
+ */
+
+extern void iommu_init(int iommu_node, struct sbus_bus *sbus);
+extern void iounit_init(int sbi_node, int iounit_node, struct sbus_bus *sbus);
+void sun4_init(void);
+#ifdef CONFIG_SUN_AUXIO
+extern void auxio_probe(void);
+#endif
+
+static void __init sbus_do_child_siblings(int start_node,
+					  struct sbus_dev *child,
+					  struct sbus_dev *parent,
+					  struct sbus_bus *sbus)
+{
+	struct sbus_dev *this_dev = child;
+	int this_node = start_node;
+
+	/* Child already filled in, just need to traverse siblings. */
+	child->child = NULL;
+	child->parent = parent;
+	while((this_node = prom_getsibling(this_node)) != 0) {
+		this_dev->next = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC);
+		this_dev = this_dev->next;
+		this_dev->next = NULL;
+		this_dev->parent = parent;
+
+		this_dev->bus = sbus;
+		fill_sbus_device(this_node, this_dev);
+
+		if(prom_getchild(this_node)) {
+			this_dev->child = kmalloc(sizeof(struct sbus_dev),
+						  GFP_ATOMIC);
+			this_dev->child->bus = sbus;
+			this_dev->child->next = NULL;
+			fill_sbus_device(prom_getchild(this_node), this_dev->child);
+			sbus_do_child_siblings(prom_getchild(this_node),
+					       this_dev->child, this_dev, sbus);
+		} else {
+			this_dev->child = NULL;
+		}
+	}
+}
+
+/*
+ * XXX This functions appears to be a distorted version of
+ * prom_sbus_ranges_init(), with all sun4d stuff cut away.
+ * Ask DaveM what is going on here, how is sun4d supposed to work... XXX
+ */
+/* added back sun4d patch from Thomas Bogendoerfer - should be OK (crn) */
+
+static void __init sbus_bus_ranges_init(int parent_node, struct sbus_bus *sbus)
+{
+	int len;
+
+	len = prom_getproperty(sbus->prom_node, "ranges",
+			       (char *) sbus->sbus_ranges,
+			       sizeof(sbus->sbus_ranges));
+	if (len == -1 || len == 0) {
+		sbus->num_sbus_ranges = 0;
+		return;
+	}
+	sbus->num_sbus_ranges = len / sizeof(struct linux_prom_ranges);
+#ifdef CONFIG_SPARC32
+	if (sparc_cpu_model == sun4d) {
+		struct linux_prom_ranges iounit_ranges[PROMREG_MAX];
+		int num_iounit_ranges;
+
+		len = prom_getproperty(parent_node, "ranges",
+				       (char *) iounit_ranges,
+				       sizeof (iounit_ranges));
+		if (len != -1) {
+			num_iounit_ranges = (len/sizeof(struct linux_prom_ranges));
+			prom_adjust_ranges (sbus->sbus_ranges, sbus->num_sbus_ranges, iounit_ranges, num_iounit_ranges);
+		}
+	}
+#endif
+}
+
+static void __init __apply_ranges_to_regs(struct linux_prom_ranges *ranges,
+					  int num_ranges,
+					  struct linux_prom_registers *regs,
+					  int num_regs)
+{
+	if (num_ranges) {
+		int regnum;
+
+		for (regnum = 0; regnum < num_regs; regnum++) {
+			int rngnum;
+
+			for (rngnum = 0; rngnum < num_ranges; rngnum++) {
+				if (regs[regnum].which_io == ranges[rngnum].ot_child_space)
+					break;
+			}
+			if (rngnum == num_ranges) {
+				/* We used to flag this as an error.  Actually
+				 * some devices do not report the regs as we expect.
+				 * For example, see SUNW,pln device.  In that case
+				 * the reg property is in a format internal to that
+				 * node, ie. it is not in the SBUS register space
+				 * per se. -DaveM
+				 */
+				return;
+			}
+			regs[regnum].which_io = ranges[rngnum].ot_parent_space;
+			regs[regnum].phys_addr -= ranges[rngnum].ot_child_base;
+			regs[regnum].phys_addr += ranges[rngnum].ot_parent_base;
+		}
+	}
+}
+
+static void __init __fixup_regs_sdev(struct sbus_dev *sdev)
+{
+	if (sdev->num_registers != 0) {
+		struct sbus_dev *parent = sdev->parent;
+		int i;
+
+		while (parent != NULL) {
+			__apply_ranges_to_regs(parent->device_ranges,
+					       parent->num_device_ranges,
+					       sdev->reg_addrs,
+					       sdev->num_registers);
+
+			parent = parent->parent;
+		}
+
+		__apply_ranges_to_regs(sdev->bus->sbus_ranges,
+				       sdev->bus->num_sbus_ranges,
+				       sdev->reg_addrs,
+				       sdev->num_registers);
+
+		for (i = 0; i < sdev->num_registers; i++) {
+			struct resource *res = &sdev->resource[i];
+
+			res->start = sdev->reg_addrs[i].phys_addr;
+			res->end = (res->start +
+				    (unsigned long)sdev->reg_addrs[i].reg_size - 1UL);
+			res->flags = IORESOURCE_IO |
+				(sdev->reg_addrs[i].which_io & 0xff);
+		}
+	}
+}
+
+static void __init sbus_fixup_all_regs(struct sbus_dev *first_sdev)
+{
+	struct sbus_dev *sdev;
+
+	for (sdev = first_sdev; sdev; sdev = sdev->next) {
+		if (sdev->child)
+			sbus_fixup_all_regs(sdev->child);
+		__fixup_regs_sdev(sdev);
+	}
+}
+
+extern void register_proc_sparc_ioport(void);
+extern void firetruck_init(void);
+
+#ifdef CONFIG_SUN4
+extern void sun4_dvma_init(void);
+#endif
+
+static int __init sbus_init(void)
+{
+	int nd, this_sbus, sbus_devs, topnd, iommund;
+	unsigned int sbus_clock;
+	struct sbus_bus *sbus;
+	struct sbus_dev *this_dev;
+	int num_sbus = 0;  /* How many did we find? */
+
+#ifdef CONFIG_SPARC32
+	register_proc_sparc_ioport();
+#endif
+
+#ifdef CONFIG_SUN4
+	sun4_dvma_init();
+	return 0;
+#endif
+
+	topnd = prom_getchild(prom_root_node);
+	
+	/* Finding the first sbus is a special case... */
+	iommund = 0;
+	if(sparc_cpu_model == sun4u) {
+		nd = prom_searchsiblings(topnd, "sbus");
+		if(nd == 0) {
+#ifdef CONFIG_PCI
+			if (!pcic_present()) {
+				prom_printf("Neither SBUS nor PCI found.\n");
+				prom_halt();
+			} else {
+#ifdef CONFIG_SPARC64
+				firetruck_init();
+#endif
+			}
+			return 0;
+#else
+			prom_printf("YEEE, UltraSparc sbus not found\n");
+			prom_halt();
+#endif
+		}
+	} else if(sparc_cpu_model == sun4d) {
+		if((iommund = prom_searchsiblings(topnd, "io-unit")) == 0 ||
+		   (nd = prom_getchild(iommund)) == 0 ||
+		   (nd = prom_searchsiblings(nd, "sbi")) == 0) {
+		   	panic("sbi not found");
+		}
+	} else if((nd = prom_searchsiblings(topnd, "sbus")) == 0) {
+		if((iommund = prom_searchsiblings(topnd, "iommu")) == 0 ||
+		   (nd = prom_getchild(iommund)) == 0 ||
+		   (nd = prom_searchsiblings(nd, "sbus")) == 0) {
+#ifdef CONFIG_PCI
+                        if (!pcic_present()) {
+                                prom_printf("Neither SBUS nor PCI found.\n");
+                                prom_halt();
+                        }
+                        return 0;
+#else
+			/* No reason to run further - the data access trap will occur. */
+			panic("sbus not found");
+#endif
+		}
+	}
+
+	/* Ok, we've found the first one, allocate first SBus struct
+	 * and place in chain.
+	 */
+	sbus = sbus_root = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC);
+	sbus->next = NULL;
+	sbus->prom_node = nd;
+	this_sbus = nd;
+
+	if(iommund && sparc_cpu_model != sun4u && sparc_cpu_model != sun4d)
+		iommu_init(iommund, sbus);
+
+	/* Loop until we find no more SBUS's */
+	while(this_sbus) {
+#ifdef CONFIG_SPARC64
+		/* IOMMU hides inside SBUS/SYSIO prom node on Ultra. */
+		if(sparc_cpu_model == sun4u) {
+			extern void sbus_iommu_init(int prom_node, struct sbus_bus *sbus);
+
+			sbus_iommu_init(this_sbus, sbus);
+		}
+#endif /* CONFIG_SPARC64 */
+
+#ifdef CONFIG_SPARC32
+		if (sparc_cpu_model == sun4d)
+			iounit_init(this_sbus, iommund, sbus);
+#endif /* CONFIG_SPARC32 */
+		printk("sbus%d: ", num_sbus);
+		sbus_clock = prom_getint(this_sbus, "clock-frequency");
+		if(sbus_clock == -1)
+			sbus_clock = (25*1000*1000);
+		printk("Clock %d.%d MHz\n", (int) ((sbus_clock/1000)/1000),
+		       (int) (((sbus_clock/1000)%1000 != 0) ? 
+			      (((sbus_clock/1000)%1000) + 1000) : 0));
+
+		prom_getstring(this_sbus, "name",
+			       sbus->prom_name, sizeof(sbus->prom_name));
+		sbus->clock_freq = sbus_clock;
+#ifdef CONFIG_SPARC32
+		if (sparc_cpu_model == sun4d) {
+			sbus->devid = prom_getint(iommund, "device-id");
+			sbus->board = prom_getint(iommund, "board#");
+		}
+#endif
+		
+		sbus_bus_ranges_init(iommund, sbus);
+
+		sbus_devs = prom_getchild(this_sbus);
+		if (!sbus_devs) {
+			sbus->devices = NULL;
+			goto next_bus;
+		}
+
+		sbus->devices = kmalloc(sizeof(struct sbus_dev), GFP_ATOMIC);
+
+		this_dev = sbus->devices;
+		this_dev->next = NULL;
+
+		this_dev->bus = sbus;
+		this_dev->parent = NULL;
+		fill_sbus_device(sbus_devs, this_dev);
+
+		/* Should we traverse for children? */
+		if(prom_getchild(sbus_devs)) {
+			/* Allocate device node */
+			this_dev->child = kmalloc(sizeof(struct sbus_dev),
+						  GFP_ATOMIC);
+			/* Fill it */
+			this_dev->child->bus = sbus;
+			this_dev->child->next = NULL;
+			fill_sbus_device(prom_getchild(sbus_devs),
+					 this_dev->child);
+			sbus_do_child_siblings(prom_getchild(sbus_devs),
+					       this_dev->child,
+					       this_dev,
+					       sbus);
+		} else {
+			this_dev->child = NULL;
+		}
+
+		while((sbus_devs = prom_getsibling(sbus_devs)) != 0) {
+			/* Allocate device node */
+			this_dev->next = kmalloc(sizeof(struct sbus_dev),
+						 GFP_ATOMIC);
+			this_dev = this_dev->next;
+			this_dev->next = NULL;
+
+			/* Fill it */
+			this_dev->bus = sbus;
+			this_dev->parent = NULL;
+			fill_sbus_device(sbus_devs, this_dev);
+
+			/* Is there a child node hanging off of us? */
+			if(prom_getchild(sbus_devs)) {
+				/* Get new device struct */
+				this_dev->child = kmalloc(sizeof(struct sbus_dev),
+							  GFP_ATOMIC);
+				/* Fill it */
+				this_dev->child->bus = sbus;
+				this_dev->child->next = NULL;
+				fill_sbus_device(prom_getchild(sbus_devs),
+						 this_dev->child);
+				sbus_do_child_siblings(prom_getchild(sbus_devs),
+						       this_dev->child,
+						       this_dev,
+						       sbus);
+			} else {
+				this_dev->child = NULL;
+			}
+		}
+
+		/* Walk all devices and apply parent ranges. */
+		sbus_fixup_all_regs(sbus->devices);
+
+		dvma_init(sbus);
+	next_bus:
+		num_sbus++;
+		if(sparc_cpu_model == sun4u) {
+			this_sbus = prom_getsibling(this_sbus);
+			if(!this_sbus)
+				break;
+			this_sbus = prom_searchsiblings(this_sbus, "sbus");
+		} else if(sparc_cpu_model == sun4d) {
+			iommund = prom_getsibling(iommund);
+			if(!iommund)
+				break;
+			iommund = prom_searchsiblings(iommund, "io-unit");
+			if(!iommund)
+				break;
+			this_sbus = prom_searchsiblings(prom_getchild(iommund), "sbi");
+		} else {
+			this_sbus = prom_getsibling(this_sbus);
+			if(!this_sbus)
+				break;
+			this_sbus = prom_searchsiblings(this_sbus, "sbus");
+		}
+		if(this_sbus) {
+			sbus->next = kmalloc(sizeof(struct sbus_bus), GFP_ATOMIC);
+			sbus = sbus->next;
+			sbus->next = NULL;
+			sbus->prom_node = this_sbus;
+		} else {
+			break;
+		}
+	} /* while(this_sbus) */
+
+	if (sparc_cpu_model == sun4d) {
+		extern void sun4d_init_sbi_irq(void);
+		sun4d_init_sbi_irq();
+	}
+	
+#ifdef CONFIG_SPARC64
+	if (sparc_cpu_model == sun4u) {
+		firetruck_init();
+	}
+#endif
+#ifdef CONFIG_SUN_AUXIO
+	if (sparc_cpu_model == sun4u)
+		auxio_probe ();
+#endif
+#ifdef CONFIG_SPARC64
+	if (sparc_cpu_model == sun4u) {
+		extern void clock_probe(void);
+
+		clock_probe();
+	}
+#endif
+
+	return 0;
+}
+
+subsys_initcall(sbus_init);