v4.19.13 snapshot.
diff --git a/drivers/sbus/Makefile b/drivers/sbus/Makefile
new file mode 100644
index 0000000..e94dc25
--- /dev/null
+++ b/drivers/sbus/Makefile
@@ -0,0 +1,5 @@
+#
+# Makefile for the linux kernel.
+#
+
+obj-$(CONFIG_SBUSCHAR) += char/
diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig
new file mode 100644
index 0000000..89edd13
--- /dev/null
+++ b/drivers/sbus/char/Kconfig
@@ -0,0 +1,76 @@
+
+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 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 TADPOLE_TS102_UCTRL
+	tristate "Tadpole TS102 Microcontroller support"
+	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 BBC_I2C
+	tristate "UltraSPARC-III bootbus i2c controller driver"
+	depends on PCI && SPARC64
+	help
+	  The BBC devices on the UltraSPARC III have two I2C controllers.  The
+	  first I2C controller connects mainly to configuration PROMs (NVRAM,
+	  CPU configuration, DIMM types, etc.).  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.  Say Y to enable support for these.
+
+config ENVCTRL
+	tristate "SUNW, envctrl support"
+	depends on PCI && SPARC64
+	help
+	  Kernel support for temperature and fan monitoring on Sun SME
+	  machines.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called envctrl.
+
+config DISPLAY7SEG
+	tristate "7-Segment Display support"
+	depends on PCI && SPARC64
+	---help---
+	  This is the driver for the 7-segment display and LED present on
+	  Sun Microsystems CompactPCI models CP1400 and CP1500.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called display7seg.
+
+	  If you do not have a CompactPCI model CP1400 or CP1500, or
+	  another UltraSPARC-IIi-cEngine boardset with a 7-segment display,
+	  you should say N to this option.
+
+config ORACLE_DAX
+	tristate "Oracle Data Analytics Accelerator"
+	depends on SPARC64
+	default m
+	help
+	 Driver for Oracle Data Analytics Accelerator, which is
+	 a coprocessor that performs database operations in hardware.
+	 It is available on M7 and M8 based systems only.
+
+endmenu
+
diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile
new file mode 100644
index 0000000..44347c9
--- /dev/null
+++ b/drivers/sbus/char/Makefile
@@ -0,0 +1,19 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# 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.
+#
+
+bbc-objs := bbc_i2c.o bbc_envctrl.o
+
+obj-$(CONFIG_ENVCTRL)			+= envctrl.o
+obj-$(CONFIG_DISPLAY7SEG)		+= display7seg.o
+obj-$(CONFIG_OBP_FLASH)			+= flash.o
+obj-$(CONFIG_SUN_OPENPROMIO)		+= openprom.o
+obj-$(CONFIG_TADPOLE_TS102_UCTRL)	+= uctrl.o
+obj-$(CONFIG_BBC_I2C)			+= bbc.o
+obj-$(CONFIG_ORACLE_DAX) 		+= oradax.o
diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c
new file mode 100644
index 0000000..fb5bcf6
--- /dev/null
+++ b/drivers/sbus/char/bbc_envctrl.c
@@ -0,0 +1,601 @@
+// SPDX-License-Identifier: GPL-2.0
+/* bbc_envctrl.c: UltraSPARC-III environment control driver.
+ *
+ * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net)
+ */
+
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/kmod.h>
+#include <linux/reboot.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <asm/oplib.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 },
+};
+
+static LIST_HEAD(all_temps);
+static LIST_HEAD(all_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;
+	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;
+	orderly_poweroff(true);
+}
+
+#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.
+	 */
+	list_for_each_entry(tp, &all_temps, glob_list) {
+		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.
+	 */
+	list_for_each_entry(fp, &all_fans, glob_list) {
+		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)
+{
+	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 (kthread_should_stop())
+			break;
+
+		list_for_each_entry(tp, &all_temps, glob_list) {
+			get_current_temps(tp);
+			analyze_temps(tp, &last_warning_jiffies);
+		}
+		list_for_each_entry(fp, &all_fans, glob_list)
+			maybe_new_fan_speeds(fp);
+	}
+	printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n");
+
+	fans_full_blast();
+
+	return 0;
+}
+
+static void attach_one_temp(struct bbc_i2c_bus *bp, struct platform_device *op,
+			    int temp_idx)
+{
+	struct bbc_cpu_temperature *tp;
+
+	tp = kzalloc(sizeof(*tp), GFP_KERNEL);
+	if (!tp)
+		return;
+
+	INIT_LIST_HEAD(&tp->bp_list);
+	INIT_LIST_HEAD(&tp->glob_list);
+
+	tp->client = bbc_i2c_attach(bp, op);
+	if (!tp->client) {
+		kfree(tp);
+		return;
+	}
+
+
+	tp->index = temp_idx;
+
+	list_add(&tp->glob_list, &all_temps);
+	list_add(&tp->bp_list, &bp->temps);
+
+	/* 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 bbc_i2c_bus *bp, struct platform_device *op,
+			   int fan_idx)
+{
+	struct bbc_fan_control *fp;
+
+	fp = kzalloc(sizeof(*fp), GFP_KERNEL);
+	if (!fp)
+		return;
+
+	INIT_LIST_HEAD(&fp->bp_list);
+	INIT_LIST_HEAD(&fp->glob_list);
+
+	fp->client = bbc_i2c_attach(bp, op);
+	if (!fp->client) {
+		kfree(fp);
+		return;
+	}
+
+	fp->index = fan_idx;
+
+	list_add(&fp->glob_list, &all_fans);
+	list_add(&fp->bp_list, &bp->fans);
+
+	/* 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);
+}
+
+static void destroy_one_temp(struct bbc_cpu_temperature *tp)
+{
+	bbc_i2c_detach(tp->client);
+	kfree(tp);
+}
+
+static void destroy_all_temps(struct bbc_i2c_bus *bp)
+{
+	struct bbc_cpu_temperature *tp, *tpos;
+
+	list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) {
+		list_del(&tp->bp_list);
+		list_del(&tp->glob_list);
+		destroy_one_temp(tp);
+	}
+}
+
+static void destroy_one_fan(struct bbc_fan_control *fp)
+{
+	bbc_i2c_detach(fp->client);
+	kfree(fp);
+}
+
+static void destroy_all_fans(struct bbc_i2c_bus *bp)
+{
+	struct bbc_fan_control *fp, *fpos;
+
+	list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) {
+		list_del(&fp->bp_list);
+		list_del(&fp->glob_list);
+		destroy_one_fan(fp);
+	}
+}
+
+int bbc_envctrl_init(struct bbc_i2c_bus *bp)
+{
+	struct platform_device *op;
+	int temp_index = 0;
+	int fan_index = 0;
+	int devidx = 0;
+
+	while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) {
+		if (!strcmp(op->dev.of_node->name, "temperature"))
+			attach_one_temp(bp, op, temp_index++);
+		if (!strcmp(op->dev.of_node->name, "fan-control"))
+			attach_one_fan(bp, op, fan_index++);
+	}
+	if (temp_index != 0 && fan_index != 0) {
+		kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
+		if (IS_ERR(kenvctrld_task)) {
+			int err = PTR_ERR(kenvctrld_task);
+
+			kenvctrld_task = NULL;
+			destroy_all_temps(bp);
+			destroy_all_fans(bp);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp)
+{
+	if (kenvctrld_task)
+		kthread_stop(kenvctrld_task);
+
+	destroy_all_temps(bp);
+	destroy_all_fans(bp);
+}
diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c
new file mode 100644
index 0000000..129967a
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.c
@@ -0,0 +1,424 @@
+/* bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III
+ *            platforms.
+ *
+ * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net)
+ */
+
+#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/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <asm/bbc.h>
+#include <asm/io.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.
+ */
+
+static void set_device_claimage(struct bbc_i2c_bus *bp, struct platform_device *op, int val)
+{
+	int i;
+
+	for (i = 0; i < NUM_CHILDREN; i++) {
+		if (bp->devs[i].device == op) {
+			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)
+
+struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *bp, int index)
+{
+	struct platform_device *op = NULL;
+	int curidx = 0, i;
+
+	for (i = 0; i < NUM_CHILDREN; i++) {
+		if (!(op = bp->devs[i].device))
+			break;
+		if (curidx == index)
+			goto out;
+		op = NULL;
+		curidx++;
+	}
+
+out:
+	if (curidx == index)
+		return op;
+	return NULL;
+}
+
+struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *op)
+{
+	struct bbc_i2c_client *client;
+	const u32 *reg;
+
+	client = kzalloc(sizeof(*client), GFP_KERNEL);
+	if (!client)
+		return NULL;
+	client->bp = bp;
+	client->op = op;
+
+	reg = of_get_property(op->dev.of_node, "reg", NULL);
+	if (!reg) {
+		kfree(client);
+		return NULL;
+	}
+
+	client->bus = reg[0];
+	client->address = reg[1];
+
+	claim_device(bp, op);
+
+	return client;
+}
+
+void bbc_i2c_detach(struct bbc_i2c_client *client)
+{
+	struct bbc_i2c_bus *bp = client->bp;
+	struct platform_device *op = client->op;
+
+	release_device(bp, op);
+	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) {
+		long val;
+
+		val = wait_event_interruptible_timeout(
+				bp->wq,
+				(((*status = readb(bp->i2c_control_regs + 0))
+				  & I2C_PCF_PIN) == 0),
+				msecs_to_jiffies(250));
+		if (val > 0) {
+			ret = 0;
+			break;
+		}
+	}
+	remove_wait_queue(&bp->wq, &wait);
+	bp->waiting = 0;
+
+	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) {
+		ret = bbc_i2c_writeb(client, *buf, off);
+		if (ret < 0)
+			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) {
+		ret = bbc_i2c_readb(client, buf, off);
+		if (ret < 0)
+			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 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_interruptible(&bp->wq);
+
+	return IRQ_HANDLED;
+}
+
+static void 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 struct bbc_i2c_bus * attach_one_i2c(struct platform_device *op, int index)
+{
+	struct bbc_i2c_bus *bp;
+	struct device_node *dp;
+	int entry;
+
+	bp = kzalloc(sizeof(*bp), GFP_KERNEL);
+	if (!bp)
+		return NULL;
+
+	INIT_LIST_HEAD(&bp->temps);
+	INIT_LIST_HEAD(&bp->fans);
+
+	bp->i2c_control_regs = of_ioremap(&op->resource[0], 0, 0x2, "bbc_i2c_regs");
+	if (!bp->i2c_control_regs)
+		goto fail;
+
+	if (op->num_resources == 2) {
+		bp->i2c_bussel_reg = of_ioremap(&op->resource[1], 0, 0x1, "bbc_i2c_bussel");
+		if (!bp->i2c_bussel_reg)
+			goto fail;
+	}
+
+	bp->waiting = 0;
+	init_waitqueue_head(&bp->wq);
+	if (request_irq(op->archdata.irqs[0], bbc_i2c_interrupt,
+			IRQF_SHARED, "bbc_i2c", bp))
+		goto fail;
+
+	bp->index = index;
+	bp->op = op;
+
+	spin_lock_init(&bp->lock);
+
+	entry = 0;
+	for (dp = op->dev.of_node->child;
+	     dp && entry < 8;
+	     dp = dp->sibling, entry++) {
+		struct platform_device *child_op;
+
+		child_op = of_find_device_by_node(dp);
+		bp->devs[entry].device = child_op;
+		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 bp;
+
+fail:
+	if (bp->i2c_bussel_reg)
+		of_iounmap(&op->resource[1], bp->i2c_bussel_reg, 1);
+	if (bp->i2c_control_regs)
+		of_iounmap(&op->resource[0], bp->i2c_control_regs, 2);
+	kfree(bp);
+	return NULL;
+}
+
+extern int bbc_envctrl_init(struct bbc_i2c_bus *bp);
+extern void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp);
+
+static int bbc_i2c_probe(struct platform_device *op)
+{
+	struct bbc_i2c_bus *bp;
+	int err, index = 0;
+
+	bp = attach_one_i2c(op, index);
+	if (!bp)
+		return -EINVAL;
+
+	err = bbc_envctrl_init(bp);
+	if (err) {
+		free_irq(op->archdata.irqs[0], bp);
+		if (bp->i2c_bussel_reg)
+			of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1);
+		if (bp->i2c_control_regs)
+			of_iounmap(&op->resource[1], bp->i2c_control_regs, 2);
+		kfree(bp);
+	} else {
+		dev_set_drvdata(&op->dev, bp);
+	}
+
+	return err;
+}
+
+static int bbc_i2c_remove(struct platform_device *op)
+{
+	struct bbc_i2c_bus *bp = dev_get_drvdata(&op->dev);
+
+	bbc_envctrl_cleanup(bp);
+
+	free_irq(op->archdata.irqs[0], bp);
+
+	if (bp->i2c_bussel_reg)
+		of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1);
+	if (bp->i2c_control_regs)
+		of_iounmap(&op->resource[1], bp->i2c_control_regs, 2);
+
+	kfree(bp);
+
+	return 0;
+}
+
+static const struct of_device_id bbc_i2c_match[] = {
+	{
+		.name = "i2c",
+		.compatible = "SUNW,bbc-i2c",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, bbc_i2c_match);
+
+static struct platform_driver bbc_i2c_driver = {
+	.driver = {
+		.name = "bbc_i2c",
+		.of_match_table = bbc_i2c_match,
+	},
+	.probe		= bbc_i2c_probe,
+	.remove		= bbc_i2c_remove,
+};
+
+module_platform_driver(bbc_i2c_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/bbc_i2c.h b/drivers/sbus/char/bbc_i2c.h
new file mode 100644
index 0000000..c2d066d
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.h
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _BBC_I2C_H
+#define _BBC_I2C_H
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/list.h>
+
+struct bbc_i2c_client {
+	struct bbc_i2c_bus		*bp;
+	struct platform_device		*op;
+	int				bus;
+	int				address;
+};
+
+enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX };
+
+struct bbc_cpu_temperature {
+	struct list_head		bp_list;
+	struct list_head		glob_list;
+
+	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_fan_control {
+	struct list_head		bp_list;
+	struct list_head		glob_list;
+
+	struct bbc_i2c_client 		*client;
+	int 				index;
+
+	int				psupply_fan_on;
+	int				cpu_fan_speed;
+	int				system_fan_speed;
+};
+
+#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 list_head		temps;
+	struct list_head		fans;
+
+	struct platform_device		*op;
+	struct {
+		struct platform_device	*device;
+		int			client_claimed;
+	} devs[NUM_CHILDREN];
+};
+
+/* Probing and attachment. */
+extern struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *, int);
+extern struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *);
+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/display7seg.c b/drivers/sbus/char/display7seg.c
new file mode 100644
index 0000000..a36e4cf
--- /dev/null
+++ b/drivers/sbus/char/display7seg.c
@@ -0,0 +1,272 @@
+/* display7seg.c - Driver implementation for the 7-segment display
+ *                 present on Sun Microsystems CP1400 and CP1500
+ *
+ * Copyright (c) 2000 Eric Brower (ebrower@usa.net)
+ */
+
+#include <linux/device.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/errno.h>
+#include <linux/major.h>
+#include <linux/miscdevice.h>
+#include <linux/ioport.h>		/* request_region */
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/atomic.h>
+#include <linux/uaccess.h>		/* put_/get_user			*/
+#include <asm/io.h>
+
+#include <asm/display7seg.h>
+
+#define DRIVER_NAME	"d7s"
+#define PFX		DRIVER_NAME ": "
+
+static DEFINE_MUTEX(d7s_mutex);
+static int sol_compat = 0;		/* Solaris compatibility mode	*/
+
+/* 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");
+
+struct d7s {
+	void __iomem	*regs;
+	bool		flipped;
+};
+struct d7s *d7s_device;
+
+/*
+ * 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 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) {
+		struct d7s *p = d7s_device;
+		u8 regval = 0;
+
+		regval = readb(p->regs);
+		if (p->flipped)
+			regval |= D7S_FLIP;
+		else
+			regval &= ~D7S_FLIP;
+		writeb(regval, p->regs);
+	}
+
+	return 0;
+}
+
+static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct d7s *p = d7s_device;
+	u8 regs = readb(p->regs);
+	int error = 0;
+	u8 ireg = 0;
+
+	if (D7S_MINOR != iminor(file_inode(file)))
+		return -ENODEV;
+
+	mutex_lock(&d7s_mutex);
+	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)) {
+			error = -EFAULT;
+			break;
+		}
+		if (sol_compat) {
+			if (regs & D7S_FLIP)
+				ireg |= D7S_FLIP;
+			else
+				ireg &= ~D7S_FLIP;
+		}
+		writeb(ireg, p->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)) {
+			error = -EFAULT;
+			break;
+		}
+		break;
+
+	case D7SIOCTM:
+		/* toggle device mode-- flip display orientation */
+		regs ^= D7S_FLIP;
+		writeb(regs, p->regs);
+		break;
+	}
+	mutex_unlock(&d7s_mutex);
+
+	return error;
+}
+
+static const struct file_operations d7s_fops = {
+	.owner =		THIS_MODULE,
+	.unlocked_ioctl =	d7s_ioctl,
+	.compat_ioctl =		d7s_ioctl,
+	.open =			d7s_open,
+	.release =		d7s_release,
+	.llseek = noop_llseek,
+};
+
+static struct miscdevice d7s_miscdev = {
+	.minor		= D7S_MINOR,
+	.name		= DRIVER_NAME,
+	.fops		= &d7s_fops
+};
+
+static int d7s_probe(struct platform_device *op)
+{
+	struct device_node *opts;
+	int err = -EINVAL;
+	struct d7s *p;
+	u8 regs;
+
+	if (d7s_device)
+		goto out;
+
+	p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL);
+	err = -ENOMEM;
+	if (!p)
+		goto out;
+
+	p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s");
+	if (!p->regs) {
+		printk(KERN_ERR PFX "Cannot map chip registers\n");
+		goto out_free;
+	}
+
+	err = misc_register(&d7s_miscdev);
+	if (err) {
+		printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n",
+		       D7S_MINOR);
+		goto out_iounmap;
+	}
+
+	/* OBP option "d7s-flipped?" is honored as default for the
+	 * device, and reset default when detached
+	 */
+	regs = readb(p->regs);
+	opts = of_find_node_by_path("/options");
+	if (opts &&
+	    of_get_property(opts, "d7s-flipped?", NULL))
+		p->flipped = true;
+
+	if (p->flipped)
+		regs |= D7S_FLIP;
+	else
+		regs &= ~D7S_FLIP;
+
+	writeb(regs,  p->regs);
+
+	printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n",
+	       op->dev.of_node,
+	       (regs & D7S_FLIP) ? " (FLIPPED)" : "",
+	       op->resource[0].start,
+	       sol_compat ? "in sol_compat mode" : "");
+
+	dev_set_drvdata(&op->dev, p);
+	d7s_device = p;
+	err = 0;
+	of_node_put(opts);
+
+out:
+	return err;
+
+out_iounmap:
+	of_iounmap(&op->resource[0], p->regs, sizeof(u8));
+
+out_free:
+	goto out;
+}
+
+static int d7s_remove(struct platform_device *op)
+{
+	struct d7s *p = dev_get_drvdata(&op->dev);
+	u8 regs = readb(p->regs);
+
+	/* Honor OBP d7s-flipped? unless operating in solaris-compat mode */
+	if (sol_compat) {
+		if (p->flipped)
+			regs |= D7S_FLIP;
+		else
+			regs &= ~D7S_FLIP;
+		writeb(regs, p->regs);
+	}
+
+	misc_deregister(&d7s_miscdev);
+	of_iounmap(&op->resource[0], p->regs, sizeof(u8));
+
+	return 0;
+}
+
+static const struct of_device_id d7s_match[] = {
+	{
+		.name = "display7seg",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, d7s_match);
+
+static struct platform_driver d7s_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = d7s_match,
+	},
+	.probe		= d7s_probe,
+	.remove		= d7s_remove,
+};
+
+module_platform_driver(d7s_driver);
diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c
new file mode 100644
index 0000000..b848192
--- /dev/null
+++ b/drivers/sbus/char/envctrl.c
@@ -0,0 +1,1138 @@
+/* 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/module.h>
+#include <linux/kthread.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/miscdevice.h>
+#include <linux/kmod.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <linux/uaccess.h>
+#include <asm/envctrl.h>
+#include <asm/io.h>
+
+#define DRIVER_NAME	"envctrl"
+#define PFX		DRIVER_NAME ": "
+
+#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 temperature */
+					     /* monitor                     */
+#define ENVCTRL_VOLTAGESTAT_MON	  	5    /* voltage status monitor  */
+#define ENVCTRL_MTHRBDTEMP_MON		6    /* motherboard temperature */
+#define ENVCTRL_SCSITEMP_MON		7    /* scsi temperature */
+#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 PFX "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 PFX "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 long
+envctrl_ioctl(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 const struct file_operations envctrl_fops = {
+	.owner =		THIS_MODULE,
+	.read =			envctrl_read,
+	.unlocked_ioctl =	envctrl_ioctl,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl =		envctrl_ioctl,
+#endif
+	.open =			envctrl_open,
+	.release =		envctrl_release,
+	.llseek =		noop_llseek,
+};	
+
+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,
+			    const 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, struct device_node *dp)
+{
+	int i = 0, len;
+	const char *pos;
+	const unsigned int *pval;
+
+	/* Firmware describe channels into a stream separated by a '\0'. */
+	pos = of_get_property(dp, "channels-description", &len);
+
+	while (len > 0) {
+		int l = strlen(pos) + 1;
+		envctrl_set_mon(pchild, pos, i++);
+		len -= l;
+		pos += l;
+	}
+
+	/* Get optional properties. */
+	pval = of_get_property(dp, "warning-temp", NULL);
+	if (pval)
+		warning_temperature = *pval;
+
+	pval = of_get_property(dp, "shutdown-temp", NULL);
+	if (pval)
+		shutdown_temperature = *pval;
+}
+
+/* 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 device_node *dp,
+				   struct i2c_child_t *pchild)
+{
+	int len, i, tbls_size = 0;
+	const void *pval;
+
+	/* Get device address. */
+	pval = of_get_property(dp, "reg", &len);
+	memcpy(&pchild->addr, pval, len);
+
+	/* Get tables property.  Read firmware temperature tables. */
+	pval = of_get_property(dp, "translation", &len);
+	if (pval && len > 0) {
+		memcpy(pchild->tblprop_array, pval, len);
+                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(KERN_ERR PFX "Failed to allocate table.\n");
+			return;
+		}
+		pval = of_get_property(dp, "tables", &len);
+                if (!pval || len <= 0) {
+			printk(KERN_ERR PFX "Failed to get table.\n");
+			return;
+		}
+		memcpy(pchild->tables, pval, len);
+	}
+
+	/* 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) {
+		struct device_node *root_node;
+		int len;
+
+		root_node = of_find_node_by_path("/");
+		if (!strcmp(root_node->name, "SUNW,UltraSPARC-IIi-cEngine")) {
+			for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) {
+				pchild->mon_type[len] = ENVCTRL_NOMON;
+			}
+			of_node_put(root_node);
+			return;
+		}
+		of_node_put(root_node);
+	}
+
+	/* Get the monitor channels. */
+	pval = of_get_property(dp, "channels-in-use", &len);
+	memcpy(pchild->chnl_array, pval, len);
+	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, dp);
+			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,dp);
+			} 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;
+
+	if (inprog != 0)
+		return;
+
+	inprog = 1;
+	printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n");
+	orderly_poweroff(true);
+}
+
+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  PFX
+		       "kenvctrld unable to monitor CPU temp-- exiting\n");
+		return -ENODEV;
+	}
+
+	poll_interval = 5000; /* TODO env_mon_interval */
+
+	printk(KERN_INFO PFX "%s starting...\n", current->comm);
+	for (;;) {
+		msleep_interruptible(poll_interval);
+
+		if (kthread_should_stop())
+			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 PFX "%s exiting...\n", current->comm);
+	return 0;
+}
+
+static int envctrl_probe(struct platform_device *op)
+{
+	struct device_node *dp;
+	int index, err;
+
+	if (i2c)
+		return -EINVAL;
+
+	i2c = of_ioremap(&op->resource[0], 0, 0x2, DRIVER_NAME);
+	if (!i2c)
+		return -ENOMEM;
+
+	index = 0;
+	dp = op->dev.of_node->child;
+	while (dp) {
+		if (!strcmp(dp->name, "gpio")) {
+			i2c_childlist[index].i2ctype = I2C_GPIO;
+			envctrl_init_i2c_child(dp, &(i2c_childlist[index++]));
+		} else if (!strcmp(dp->name, "adc")) {
+			i2c_childlist[index].i2ctype = I2C_ADC;
+			envctrl_init_i2c_child(dp, &(i2c_childlist[index++]));
+		}
+
+		dp = dp->sibling;
+	}
+
+	/* 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(KERN_ERR PFX "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(KERN_INFO PFX "Initialized ");
+	for (--index; index >= 0; --index) {
+		printk("[%s 0x%lx]%s", 
+			(I2C_ADC == i2c_childlist[index].i2ctype) ? "adc" : 
+			((I2C_GPIO == i2c_childlist[index].i2ctype) ? "gpio" : "unknown"), 
+			i2c_childlist[index].addr, (0 == index) ? "\n" : " ");
+	}
+
+	kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
+	if (IS_ERR(kenvctrld_task)) {
+		err = PTR_ERR(kenvctrld_task);
+		goto out_deregister;
+	}
+
+	return 0;
+
+out_deregister:
+	misc_deregister(&envctrl_dev);
+out_iounmap:
+	of_iounmap(&op->resource[0], i2c, 0x2);
+	for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++)
+		kfree(i2c_childlist[index].tables);
+
+	return err;
+}
+
+static int envctrl_remove(struct platform_device *op)
+{
+	int index;
+
+	kthread_stop(kenvctrld_task);
+
+	of_iounmap(&op->resource[0], i2c, 0x2);
+	misc_deregister(&envctrl_dev);
+
+	for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++)
+		kfree(i2c_childlist[index].tables);
+
+	return 0;
+}
+
+static const struct of_device_id envctrl_match[] = {
+	{
+		.name = "i2c",
+		.compatible = "i2cpcf,8584",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, envctrl_match);
+
+static struct platform_driver envctrl_driver = {
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = envctrl_match,
+	},
+	.probe		= envctrl_probe,
+	.remove		= envctrl_remove,
+};
+
+module_platform_driver(envctrl_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c
new file mode 100644
index 0000000..a610b8d
--- /dev/null
+++ b/drivers/sbus/char/flash.c
@@ -0,0 +1,218 @@
+/* flash.c: Allow mmap access to the OBP Flash, for OBP updates.
+ *
+ * Copyright (C) 1997  Eddie C. Dost  (ecd@skynet.be)
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <linux/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/upa.h>
+
+static DEFINE_MUTEX(flash_mutex);
+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));
+
+	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+	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)
+{
+	mutex_lock(&flash_mutex);
+	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:
+			mutex_unlock(&flash_mutex);
+			return -EINVAL;
+	}
+	mutex_unlock(&flash_mutex);
+	return file->f_pos;
+}
+
+static ssize_t
+flash_read(struct file * file, char __user * buf,
+	   size_t count, loff_t *ppos)
+{
+	loff_t p = *ppos;
+	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++;
+	}
+
+	*ppos += count;
+	return count;
+}
+
+static int
+flash_open(struct inode *inode, struct file *file)
+{
+	mutex_lock(&flash_mutex);
+	if (test_and_set_bit(0, (void *)&flash.busy) != 0) {
+		mutex_unlock(&flash_mutex);
+		return -EBUSY;
+	}
+
+	mutex_unlock(&flash_mutex);
+	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 const 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 flash_probe(struct platform_device *op)
+{
+	struct device_node *dp = op->dev.of_node;
+	struct device_node *parent;
+
+	parent = dp->parent;
+
+	if (strcmp(parent->name, "sbus") &&
+	    strcmp(parent->name, "sbi") &&
+	    strcmp(parent->name, "ebus"))
+		return -ENODEV;
+
+	flash.read_base = op->resource[0].start;
+	flash.read_size = resource_size(&op->resource[0]);
+	if (op->resource[1].flags) {
+		flash.write_base = op->resource[1].start;
+		flash.write_size = resource_size(&op->resource[1]);
+	} else {
+		flash.write_base = op->resource[0].start;
+		flash.write_size = resource_size(&op->resource[0]);
+	}
+	flash.busy = 0;
+
+	printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
+	       op->dev.of_node,
+	       flash.read_base, flash.read_size,
+	       flash.write_base, flash.write_size);
+
+	return misc_register(&flash_dev);
+}
+
+static int flash_remove(struct platform_device *op)
+{
+	misc_deregister(&flash_dev);
+
+	return 0;
+}
+
+static const struct of_device_id flash_match[] = {
+	{
+		.name = "flashprom",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, flash_match);
+
+static struct platform_driver flash_driver = {
+	.driver = {
+		.name = "flash",
+		.of_match_table = flash_match,
+	},
+	.probe		= flash_probe,
+	.remove		= flash_remove,
+};
+
+module_platform_driver(flash_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h
new file mode 100644
index 0000000..45c8318
--- /dev/null
+++ b/drivers/sbus/char/max1617.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* $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 changeable 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..050879a
--- /dev/null
+++ b/drivers/sbus/char/openprom.c
@@ -0,0 +1,739 @@
+/*
+ * 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. 
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/miscdevice.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <asm/oplib.h>
+#include <asm/prom.h>
+#include <linux/uaccess.h>
+#include <asm/openpromio.h>
+#ifdef CONFIG_PCI
+#include <linux/pci.h>
+#endif
+
+MODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost  (ecd@skynet.be)");
+MODULE_DESCRIPTION("OPENPROM Configuration Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR);
+
+/* Private data kept by the driver for each descriptor. */
+typedef struct openprom_private_data
+{
+	struct device_node *current_node; /* Current node for SunOS ioctls. */
+	struct device_node *lastnode; /* Last valid node used by BSD ioctls. */
+} DATA;
+
+/* ID of the PROM node containing all of the EEPROM options. */
+static DEFINE_MUTEX(openprom_mutex);
+static struct device_node *options_node;
+
+/*
+ * 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 = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL)))
+		return -ENOMEM;
+
+	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 = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL)))
+		return -ENOMEM;
+
+	(*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;
+}
+
+static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
+{
+	const void *pval;
+	int len;
+
+	if (!dp ||
+	    !(pval = of_get_property(dp, op->oprom_array, &len)) ||
+	    len <= 0 || len > bufsize)
+		return copyout(argp, op, sizeof(int));
+
+	memcpy(op->oprom_array, pval, len);
+	op->oprom_array[len] = '\0';
+	op->oprom_size = len;
+
+	return copyout(argp, op, sizeof(int) + bufsize);
+}
+
+static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize)
+{
+	struct property *prop;
+	int len;
+
+	if (!dp)
+		return copyout(argp, op, sizeof(int));
+	if (op->oprom_array[0] == '\0') {
+		prop = dp->properties;
+		if (!prop)
+			return copyout(argp, op, sizeof(int));
+		len = strlen(prop->name);
+	} else {
+		prop = of_find_property(dp, op->oprom_array, NULL);
+
+		if (!prop ||
+		    !prop->next ||
+		    (len = strlen(prop->next->name)) + 1 > bufsize)
+			return copyout(argp, op, sizeof(int));
+
+		prop = prop->next;
+	}
+
+	memcpy(op->oprom_array, prop->name, len);
+	op->oprom_array[len] = '\0';
+	op->oprom_size = ++len;
+
+	return copyout(argp, op, sizeof(int) + bufsize);
+}
+
+static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize)
+{
+	char *buf = op->oprom_array + strlen(op->oprom_array) + 1;
+	int len = op->oprom_array + bufsize - buf;
+
+	return of_set_property(options_node, op->oprom_array, buf, len);
+}
+
+static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
+{
+	phandle ph;
+
+	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
+
+	if (bufsize < sizeof(phandle))
+		return -EINVAL;
+
+	ph = *((int *) op->oprom_array);
+	if (ph) {
+		dp = of_find_node_by_phandle(ph);
+		if (!dp)
+			return -EINVAL;
+
+		switch (cmd) {
+		case OPROMNEXT:
+			dp = dp->sibling;
+			break;
+
+		case OPROMCHILD:
+			dp = dp->child;
+			break;
+
+		case OPROMSETCUR:
+		default:
+			break;
+		}
+	} else {
+		/* Sibling of node zero is the root node.  */
+		if (cmd != OPROMNEXT)
+			return -EINVAL;
+
+		dp = of_find_node_by_path("/");
+	}
+
+	ph = 0;
+	if (dp)
+		ph = dp->phandle;
+
+	data->current_node = dp;
+	*((int *) op->oprom_array) = ph;
+	op->oprom_size = sizeof(phandle);
+
+	return copyout(argp, op, bufsize + sizeof(int));
+}
+
+static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
+{
+	int err = -EINVAL;
+
+	if (bufsize >= 2*sizeof(int)) {
+#ifdef CONFIG_PCI
+		struct pci_dev *pdev;
+		struct device_node *dp;
+
+		pdev = pci_get_domain_bus_and_slot(0,
+						((int *) op->oprom_array)[0],
+						((int *) op->oprom_array)[1]);
+
+		dp = pci_device_to_OF_node(pdev);
+		data->current_node = dp;
+		*((int *)op->oprom_array) = dp->phandle;
+		op->oprom_size = sizeof(int);
+		err = copyout(argp, op, bufsize + sizeof(int));
+
+		pci_dev_put(pdev);
+#endif
+	}
+
+	return err;
+}
+
+static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data)
+{
+	phandle ph = 0;
+
+	dp = of_find_node_by_path(op->oprom_array);
+	if (dp)
+		ph = dp->phandle;
+	data->current_node = dp;
+	*((int *)op->oprom_array) = ph;
+	op->oprom_size = sizeof(int);
+
+	return copyout(argp, op, bufsize + sizeof(int));
+}
+
+static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize)
+{
+	char *buf = saved_command_line;
+	int len = strlen(buf);
+
+	if (len > bufsize)
+		return -EINVAL;
+
+	strcpy(op->oprom_array, buf);
+	op->oprom_size = len;
+
+	return copyout(argp, op, bufsize + sizeof(int));
+}
+
+/*
+ *	SunOS and Solaris /dev/openprom ioctl calls.
+ */
+static long openprom_sunos_ioctl(struct file * file,
+				 unsigned int cmd, unsigned long arg,
+				 struct device_node *dp)
+{
+	DATA *data = file->private_data;
+	struct openpromio *opp = NULL;
+	int bufsize, 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;
+
+	mutex_lock(&openprom_mutex);
+
+	switch (cmd) {
+	case OPROMGETOPT:
+	case OPROMGETPROP:
+		error = opromgetprop(argp, dp, opp, bufsize);
+		break;
+
+	case OPROMNXTOPT:
+	case OPROMNXTPROP:
+		error = opromnxtprop(argp, dp, opp, bufsize);
+		break;
+
+	case OPROMSETOPT:
+	case OPROMSETOPT2:
+		error = opromsetopt(dp, opp, bufsize);
+		break;
+
+	case OPROMNEXT:
+	case OPROMCHILD:
+	case OPROMSETCUR:
+		error = opromnext(argp, cmd, dp, opp, bufsize, data);
+		break;
+
+	case OPROMPCI2NODE:
+		error = oprompci2node(argp, dp, opp, bufsize, data);
+		break;
+
+	case OPROMPATH2NODE:
+		error = oprompath2node(argp, dp, opp, bufsize, data);
+		break;
+
+	case OPROMGETBOOTARGS:
+		error = opromgetbootargs(argp, opp, bufsize);
+		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);
+	mutex_unlock(&openprom_mutex);
+
+	return error;
+}
+
+static struct device_node *get_node(phandle n, DATA *data)
+{
+	struct device_node *dp = of_find_node_by_phandle(n);
+
+	if (dp)
+		data->lastnode = dp;
+
+	return dp;
+}
+
+/* Copy in a whole string from userspace into kernelspace. */
+static char * copyin_string(char __user *user, size_t len)
+{
+	if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
+		return ERR_PTR(-EINVAL);
+
+	return memdup_user_nul(user, len);
+}
+
+/*
+ *	NetBSD /dev/openprom ioctl calls.
+ */
+static int opiocget(void __user *argp, DATA *data)
+{
+	struct opiocdesc op;
+	struct device_node *dp;
+	char *str;
+	const void *pval;
+	int err, len;
+
+	if (copy_from_user(&op, argp, sizeof(op)))
+		return -EFAULT;
+
+	dp = get_node(op.op_nodeid, data);
+
+	str = copyin_string(op.op_name, op.op_namelen);
+	if (IS_ERR(str))
+		return PTR_ERR(str);
+
+	pval = of_get_property(dp, str, &len);
+	err = 0;
+	if (!pval || len > op.op_buflen) {
+		err = -EINVAL;
+	} else {
+		op.op_buflen = len;
+		if (copy_to_user(argp, &op, sizeof(op)) ||
+		    copy_to_user(op.op_buf, pval, len))
+			err = -EFAULT;
+	}
+	kfree(str);
+
+	return err;
+}
+
+static int opiocnextprop(void __user *argp, DATA *data)
+{
+	struct opiocdesc op;
+	struct device_node *dp;
+	struct property *prop;
+	char *str;
+	int len;
+
+	if (copy_from_user(&op, argp, sizeof(op)))
+		return -EFAULT;
+
+	dp = get_node(op.op_nodeid, data);
+	if (!dp)
+		return -EINVAL;
+
+	str = copyin_string(op.op_name, op.op_namelen);
+	if (IS_ERR(str))
+		return PTR_ERR(str);
+
+	if (str[0] == '\0') {
+		prop = dp->properties;
+	} else {
+		prop = of_find_property(dp, str, NULL);
+		if (prop)
+			prop = prop->next;
+	}
+	kfree(str);
+
+	if (!prop)
+		len = 0;
+	else
+		len = prop->length;
+
+	if (len > op.op_buflen)
+		len = op.op_buflen;
+
+	if (copy_to_user(argp, &op, sizeof(op)))
+		return -EFAULT;
+
+	if (len &&
+	    copy_to_user(op.op_buf, prop->value, len))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int opiocset(void __user *argp, DATA *data)
+{
+	struct opiocdesc op;
+	struct device_node *dp;
+	char *str, *tmp;
+	int err;
+
+	if (copy_from_user(&op, argp, sizeof(op)))
+		return -EFAULT;
+
+	dp = get_node(op.op_nodeid, data);
+	if (!dp)
+		return -EINVAL;
+
+	str = copyin_string(op.op_name, op.op_namelen);
+	if (IS_ERR(str))
+		return PTR_ERR(str);
+
+	tmp = copyin_string(op.op_buf, op.op_buflen);
+	if (IS_ERR(tmp)) {
+		kfree(str);
+		return PTR_ERR(tmp);
+	}
+
+	err = of_set_property(dp, str, tmp, op.op_buflen);
+
+	kfree(str);
+	kfree(tmp);
+
+	return err;
+}
+
+static int opiocgetnext(unsigned int cmd, void __user *argp)
+{
+	struct device_node *dp;
+	phandle nd;
+
+	BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
+
+	if (copy_from_user(&nd, argp, sizeof(phandle)))
+		return -EFAULT;
+
+	if (nd == 0) {
+		if (cmd != OPIOCGETNEXT)
+			return -EINVAL;
+		dp = of_find_node_by_path("/");
+	} else {
+		dp = of_find_node_by_phandle(nd);
+		nd = 0;
+		if (dp) {
+			if (cmd == OPIOCGETNEXT)
+				dp = dp->sibling;
+			else
+				dp = dp->child;
+		}
+	}
+	if (dp)
+		nd = dp->phandle;
+	if (copy_to_user(argp, &nd, sizeof(phandle)))
+		return -EFAULT;
+
+	return 0;
+}
+
+static int openprom_bsd_ioctl(struct file * file,
+			      unsigned int cmd, unsigned long arg)
+{
+	DATA *data = file->private_data;
+	void __user *argp = (void __user *)arg;
+	int err;
+
+	mutex_lock(&openprom_mutex);
+	switch (cmd) {
+	case OPIOCGET:
+		err = opiocget(argp, data);
+		break;
+
+	case OPIOCNEXTPROP:
+		err = opiocnextprop(argp, data);
+		break;
+
+	case OPIOCSET:
+		err = opiocset(argp, data);
+		break;
+
+	case OPIOCGETOPTNODE:
+		BUILD_BUG_ON(sizeof(phandle) != sizeof(int));
+
+		err = 0;
+		if (copy_to_user(argp, &options_node->phandle, sizeof(phandle)))
+			err = -EFAULT;
+		break;
+
+	case OPIOCGETNEXT:
+	case OPIOCGETCHILD:
+		err = opiocgetnext(cmd, argp);
+		break;
+
+	default:
+		err = -EINVAL;
+		break;
+	}
+	mutex_unlock(&openprom_mutex);
+
+	return err;
+}
+
+
+/*
+ *	Handoff control to the correct ioctl handler.
+ */
+static long openprom_ioctl(struct file * file,
+			   unsigned int cmd, unsigned long arg)
+{
+	DATA *data = file->private_data;
+
+	switch (cmd) {
+	case OPROMGETOPT:
+	case OPROMNXTOPT:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(file, cmd, arg,
+					    options_node);
+
+	case OPROMSETOPT:
+	case OPROMSETOPT2:
+		if ((file->f_mode & FMODE_WRITE) == 0)
+			return -EPERM;
+		return openprom_sunos_ioctl(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(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(file, cmd, arg, NULL);
+
+	case OPIOCGET:
+	case OPIOCNEXTPROP:
+	case OPIOCGETOPTNODE:
+	case OPIOCGETNEXT:
+	case OPIOCGETCHILD:
+		if ((file->f_mode & FMODE_READ) == 0)
+			return -EBADF;
+		return openprom_bsd_ioctl(file,cmd,arg);
+
+	case OPIOCSET:
+		if ((file->f_mode & FMODE_WRITE) == 0)
+			return -EBADF;
+		return openprom_bsd_ioctl(file,cmd,arg);
+
+	default:
+		return -EINVAL;
+	};
+}
+
+static long openprom_compat_ioctl(struct file *file, unsigned int cmd,
+		unsigned long arg)
+{
+	long rval = -ENOTTY;
+
+	/*
+	 * SunOS/Solaris only, the NetBSD one's have embedded pointers in
+	 * the arg which we'd need to clean up...
+	 */
+	switch (cmd) {
+	case OPROMGETOPT:
+	case OPROMSETOPT:
+	case OPROMNXTOPT:
+	case OPROMSETOPT2:
+	case OPROMNEXT:
+	case OPROMCHILD:
+	case OPROMGETPROP:
+	case OPROMNXTPROP:
+	case OPROMU2P:
+	case OPROMGETCONS:
+	case OPROMGETFBNAME:
+	case OPROMGETBOOTARGS:
+	case OPROMSETCUR:
+	case OPROMPCI2NODE:
+	case OPROMPATH2NODE:
+		rval = openprom_ioctl(file, cmd, arg);
+		break;
+	}
+
+	return rval;
+}
+
+static int openprom_open(struct inode * inode, struct file * file)
+{
+	DATA *data;
+
+	data = kmalloc(sizeof(DATA), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	mutex_lock(&openprom_mutex);
+	data->current_node = of_find_node_by_path("/");
+	data->lastnode = data->current_node;
+	file->private_data = (void *) data;
+	mutex_unlock(&openprom_mutex);
+
+	return 0;
+}
+
+static int openprom_release(struct inode * inode, struct file * file)
+{
+	kfree(file->private_data);
+	return 0;
+}
+
+static const struct file_operations openprom_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.unlocked_ioctl = openprom_ioctl,
+	.compat_ioctl =	openprom_compat_ioctl,
+	.open =		openprom_open,
+	.release =	openprom_release,
+};
+
+static struct miscdevice openprom_dev = {
+	.minor		= SUN_OPENPROM_MINOR,
+	.name		= "openprom",
+	.fops		= &openprom_fops,
+};
+
+static int __init openprom_init(void)
+{
+	int err;
+
+	err = misc_register(&openprom_dev);
+	if (err)
+		return err;
+
+	options_node = of_get_child_by_name(of_find_node_by_path("/"), "options");
+	if (!options_node) {
+		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);
diff --git a/drivers/sbus/char/oradax.c b/drivers/sbus/char/oradax.c
new file mode 100644
index 0000000..6516bc3
--- /dev/null
+++ b/drivers/sbus/char/oradax.c
@@ -0,0 +1,1004 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Oracle Data Analytics Accelerator (DAX)
+ *
+ * DAX is a coprocessor which resides on the SPARC M7 (DAX1) and M8
+ * (DAX2) processor chips, and has direct access to the CPU's L3
+ * caches as well as physical memory. It can perform several
+ * operations on data streams with various input and output formats.
+ * The driver provides a transport mechanism only and has limited
+ * knowledge of the various opcodes and data formats. A user space
+ * library provides high level services and translates these into low
+ * level commands which are then passed into the driver and
+ * subsequently the hypervisor and the coprocessor.  The library is
+ * the recommended way for applications to use the coprocessor, and
+ * the driver interface is not intended for general use.
+ *
+ * See Documentation/sparc/oradax/oracle-dax.txt for more details.
+ */
+
+#include <linux/uaccess.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/cdev.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+
+#include <asm/hypervisor.h>
+#include <asm/mdesc.h>
+#include <asm/oradax.h>
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Driver for Oracle Data Analytics Accelerator");
+
+#define	DAX_DBG_FLG_BASIC	0x01
+#define	DAX_DBG_FLG_STAT	0x02
+#define	DAX_DBG_FLG_INFO	0x04
+#define	DAX_DBG_FLG_ALL		0xff
+
+#define	dax_err(fmt, ...)      pr_err("%s: " fmt "\n", __func__, ##__VA_ARGS__)
+#define	dax_info(fmt, ...)     pr_info("%s: " fmt "\n", __func__, ##__VA_ARGS__)
+
+#define	dax_dbg(fmt, ...)	do {					\
+					if (dax_debug & DAX_DBG_FLG_BASIC)\
+						dax_info(fmt, ##__VA_ARGS__); \
+				} while (0)
+#define	dax_stat_dbg(fmt, ...)	do {					\
+					if (dax_debug & DAX_DBG_FLG_STAT) \
+						dax_info(fmt, ##__VA_ARGS__); \
+				} while (0)
+#define	dax_info_dbg(fmt, ...)	do { \
+					if (dax_debug & DAX_DBG_FLG_INFO) \
+						dax_info(fmt, ##__VA_ARGS__); \
+				} while (0)
+
+#define	DAX1_MINOR		1
+#define	DAX1_MAJOR		1
+#define	DAX2_MINOR		0
+#define	DAX2_MAJOR		2
+
+#define	DAX1_STR    "ORCL,sun4v-dax"
+#define	DAX2_STR    "ORCL,sun4v-dax2"
+
+#define	DAX_CA_ELEMS		(DAX_MMAP_LEN / sizeof(struct dax_cca))
+
+#define	DAX_CCB_USEC		100
+#define	DAX_CCB_RETRIES		10000
+
+/* stream types */
+enum {
+	OUT,
+	PRI,
+	SEC,
+	TBL,
+	NUM_STREAM_TYPES
+};
+
+/* completion status */
+#define	CCA_STAT_NOT_COMPLETED	0
+#define	CCA_STAT_COMPLETED	1
+#define	CCA_STAT_FAILED		2
+#define	CCA_STAT_KILLED		3
+#define	CCA_STAT_NOT_RUN	4
+#define	CCA_STAT_PIPE_OUT	5
+#define	CCA_STAT_PIPE_SRC	6
+#define	CCA_STAT_PIPE_DST	7
+
+/* completion err */
+#define	CCA_ERR_SUCCESS		0x0	/* no error */
+#define	CCA_ERR_OVERFLOW	0x1	/* buffer overflow */
+#define	CCA_ERR_DECODE		0x2	/* CCB decode error */
+#define	CCA_ERR_PAGE_OVERFLOW	0x3	/* page overflow */
+#define	CCA_ERR_KILLED		0x7	/* command was killed */
+#define	CCA_ERR_TIMEOUT		0x8	/* Timeout */
+#define	CCA_ERR_ADI		0x9	/* ADI error */
+#define	CCA_ERR_DATA_FMT	0xA	/* data format error */
+#define	CCA_ERR_OTHER_NO_RETRY	0xE	/* Other error, do not retry */
+#define	CCA_ERR_OTHER_RETRY	0xF	/* Other error, retry */
+#define	CCA_ERR_PARTIAL_SYMBOL	0x80	/* QP partial symbol warning */
+
+/* CCB address types */
+#define	DAX_ADDR_TYPE_NONE	0
+#define	DAX_ADDR_TYPE_VA_ALT	1	/* secondary context */
+#define	DAX_ADDR_TYPE_RA	2	/* real address */
+#define	DAX_ADDR_TYPE_VA	3	/* virtual address */
+
+/* dax_header_t opcode */
+#define	DAX_OP_SYNC_NOP		0x0
+#define	DAX_OP_EXTRACT		0x1
+#define	DAX_OP_SCAN_VALUE	0x2
+#define	DAX_OP_SCAN_RANGE	0x3
+#define	DAX_OP_TRANSLATE	0x4
+#define	DAX_OP_SELECT		0x5
+#define	DAX_OP_INVERT		0x10	/* OR with translate, scan opcodes */
+
+struct dax_header {
+	u32 ccb_version:4;	/* 31:28 CCB Version */
+				/* 27:24 Sync Flags */
+	u32 pipe:1;		/* Pipeline */
+	u32 longccb:1;		/* Longccb. Set for scan with lu2, lu3, lu4. */
+	u32 cond:1;		/* Conditional */
+	u32 serial:1;		/* Serial */
+	u32 opcode:8;		/* 23:16 Opcode */
+				/* 15:0 Address Type. */
+	u32 reserved:3;		/* 15:13 reserved */
+	u32 table_addr_type:2;	/* 12:11 Huffman Table Address Type */
+	u32 out_addr_type:3;	/* 10:8 Destination Address Type */
+	u32 sec_addr_type:3;	/* 7:5 Secondary Source Address Type */
+	u32 pri_addr_type:3;	/* 4:2 Primary Source Address Type */
+	u32 cca_addr_type:2;	/* 1:0 Completion Address Type */
+};
+
+struct dax_control {
+	u32 pri_fmt:4;		/* 31:28 Primary Input Format */
+	u32 pri_elem_size:5;	/* 27:23 Primary Input Element Size(less1) */
+	u32 pri_offset:3;	/* 22:20 Primary Input Starting Offset */
+	u32 sec_encoding:1;	/* 19    Secondary Input Encoding */
+				/*	 (must be 0 for Select) */
+	u32 sec_offset:3;	/* 18:16 Secondary Input Starting Offset */
+	u32 sec_elem_size:2;	/* 15:14 Secondary Input Element Size */
+				/*	 (must be 0 for Select) */
+	u32 out_fmt:2;		/* 13:12 Output Format */
+	u32 out_elem_size:2;	/* 11:10 Output Element Size */
+	u32 misc:10;		/* 9:0 Opcode specific info */
+};
+
+struct dax_data_access {
+	u64 flow_ctrl:2;	/* 63:62 Flow Control Type */
+	u64 pipe_target:2;	/* 61:60 Pipeline Target */
+	u64 out_buf_size:20;	/* 59:40 Output Buffer Size */
+				/*	 (cachelines less 1) */
+	u64 unused1:8;		/* 39:32 Reserved, Set to 0 */
+	u64 out_alloc:5;	/* 31:27 Output Allocation */
+	u64 unused2:1;		/* 26	 Reserved */
+	u64 pri_len_fmt:2;	/* 25:24 Input Length Format */
+	u64 pri_len:24;		/* 23:0  Input Element/Byte/Bit Count */
+				/*	 (less 1) */
+};
+
+struct dax_ccb {
+	struct dax_header hdr;	/* CCB Header */
+	struct dax_control ctrl;/* Control Word */
+	void *ca;		/* Completion Address */
+	void *pri;		/* Primary Input Address */
+	struct dax_data_access dac; /* Data Access Control */
+	void *sec;		/* Secondary Input Address */
+	u64 dword5;		/* depends on opcode */
+	void *out;		/* Output Address */
+	void *tbl;		/* Table Address or bitmap */
+};
+
+struct dax_cca {
+	u8	status;		/* user may mwait on this address */
+	u8	err;		/* user visible error notification */
+	u8	rsvd[2];	/* reserved */
+	u32	n_remaining;	/* for QP partial symbol warning */
+	u32	output_sz;	/* output in bytes */
+	u32	rsvd2;		/* reserved */
+	u64	run_cycles;	/* run time in OCND2 cycles */
+	u64	run_stats;	/* nothing reported in version 1.0 */
+	u32	n_processed;	/* number input elements */
+	u32	rsvd3[5];	/* reserved */
+	u64	retval;		/* command return value */
+	u64	rsvd4[8];	/* reserved */
+};
+
+/* per thread CCB context */
+struct dax_ctx {
+	struct dax_ccb		*ccb_buf;
+	u64			ccb_buf_ra;	/* cached RA of ccb_buf  */
+	struct dax_cca		*ca_buf;
+	u64			ca_buf_ra;	/* cached RA of ca_buf   */
+	struct page		*pages[DAX_CA_ELEMS][NUM_STREAM_TYPES];
+						/* array of locked pages */
+	struct task_struct	*owner;		/* thread that owns ctx  */
+	struct task_struct	*client;	/* requesting thread     */
+	union ccb_result	result;
+	u32			ccb_count;
+	u32			fail_count;
+};
+
+/* driver public entry points */
+static int dax_open(struct inode *inode, struct file *file);
+static ssize_t dax_read(struct file *filp, char __user *buf,
+			size_t count, loff_t *ppos);
+static ssize_t dax_write(struct file *filp, const char __user *buf,
+			 size_t count, loff_t *ppos);
+static int dax_devmap(struct file *f, struct vm_area_struct *vma);
+static int dax_close(struct inode *i, struct file *f);
+
+static const struct file_operations dax_fops = {
+	.owner	=	THIS_MODULE,
+	.open	=	dax_open,
+	.read	=	dax_read,
+	.write	=	dax_write,
+	.mmap	=	dax_devmap,
+	.release =	dax_close,
+};
+
+static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf,
+			size_t count, loff_t *ppos);
+static int dax_ccb_info(u64 ca, struct ccb_info_result *info);
+static int dax_ccb_kill(u64 ca, u16 *kill_res);
+
+static struct cdev c_dev;
+static struct class *cl;
+static dev_t first;
+
+static int max_ccb_version;
+static int dax_debug;
+module_param(dax_debug, int, 0644);
+MODULE_PARM_DESC(dax_debug, "Debug flags");
+
+static int __init dax_attach(void)
+{
+	unsigned long dummy, hv_rv, major, minor, minor_requested, max_ccbs;
+	struct mdesc_handle *hp = mdesc_grab();
+	char *prop, *dax_name;
+	bool found = false;
+	int len, ret = 0;
+	u64 pn;
+
+	if (hp == NULL) {
+		dax_err("Unable to grab mdesc");
+		return -ENODEV;
+	}
+
+	mdesc_for_each_node_by_name(hp, pn, "virtual-device") {
+		prop = (char *)mdesc_get_property(hp, pn, "name", &len);
+		if (prop == NULL)
+			continue;
+		if (strncmp(prop, "dax", strlen("dax")))
+			continue;
+		dax_dbg("Found node 0x%llx = %s", pn, prop);
+
+		prop = (char *)mdesc_get_property(hp, pn, "compatible", &len);
+		if (prop == NULL)
+			continue;
+		dax_dbg("Found node 0x%llx = %s", pn, prop);
+		found = true;
+		break;
+	}
+
+	if (!found) {
+		dax_err("No DAX device found");
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if (strncmp(prop, DAX2_STR, strlen(DAX2_STR)) == 0) {
+		dax_name = DAX_NAME "2";
+		major = DAX2_MAJOR;
+		minor_requested = DAX2_MINOR;
+		max_ccb_version = 1;
+		dax_dbg("MD indicates DAX2 coprocessor");
+	} else if (strncmp(prop, DAX1_STR, strlen(DAX1_STR)) == 0) {
+		dax_name = DAX_NAME "1";
+		major = DAX1_MAJOR;
+		minor_requested = DAX1_MINOR;
+		max_ccb_version = 0;
+		dax_dbg("MD indicates DAX1 coprocessor");
+	} else {
+		dax_err("Unknown dax type: %s", prop);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	minor = minor_requested;
+	dax_dbg("Registering DAX HV api with major %ld minor %ld", major,
+		minor);
+	if (sun4v_hvapi_register(HV_GRP_DAX, major, &minor)) {
+		dax_err("hvapi_register failed");
+		ret = -ENODEV;
+		goto done;
+	} else {
+		dax_dbg("Max minor supported by HV = %ld (major %ld)", minor,
+			major);
+		minor = min(minor, minor_requested);
+		dax_dbg("registered DAX major %ld minor %ld", major, minor);
+	}
+
+	/* submit a zero length ccb array to query coprocessor queue size */
+	hv_rv = sun4v_ccb_submit(0, 0, HV_CCB_QUERY_CMD, 0, &max_ccbs, &dummy);
+	if (hv_rv != 0) {
+		dax_err("get_hwqueue_size failed with status=%ld and max_ccbs=%ld",
+			hv_rv, max_ccbs);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if (max_ccbs != DAX_MAX_CCBS) {
+		dax_err("HV reports unsupported max_ccbs=%ld", max_ccbs);
+		ret = -ENODEV;
+		goto done;
+	}
+
+	if (alloc_chrdev_region(&first, 0, 1, DAX_NAME) < 0) {
+		dax_err("alloc_chrdev_region failed");
+		ret = -ENXIO;
+		goto done;
+	}
+
+	cl = class_create(THIS_MODULE, DAX_NAME);
+	if (IS_ERR(cl)) {
+		dax_err("class_create failed");
+		ret = PTR_ERR(cl);
+		goto class_error;
+	}
+
+	if (device_create(cl, NULL, first, NULL, dax_name) == NULL) {
+		dax_err("device_create failed");
+		ret = -ENXIO;
+		goto device_error;
+	}
+
+	cdev_init(&c_dev, &dax_fops);
+	if (cdev_add(&c_dev, first, 1) == -1) {
+		dax_err("cdev_add failed");
+		ret = -ENXIO;
+		goto cdev_error;
+	}
+
+	pr_info("Attached DAX module\n");
+	goto done;
+
+cdev_error:
+	device_destroy(cl, first);
+device_error:
+	class_destroy(cl);
+class_error:
+	unregister_chrdev_region(first, 1);
+done:
+	mdesc_release(hp);
+	return ret;
+}
+module_init(dax_attach);
+
+static void __exit dax_detach(void)
+{
+	pr_info("Cleaning up DAX module\n");
+	cdev_del(&c_dev);
+	device_destroy(cl, first);
+	class_destroy(cl);
+	unregister_chrdev_region(first, 1);
+}
+module_exit(dax_detach);
+
+/* map completion area */
+static int dax_devmap(struct file *f, struct vm_area_struct *vma)
+{
+	struct dax_ctx *ctx = (struct dax_ctx *)f->private_data;
+	size_t len = vma->vm_end - vma->vm_start;
+
+	dax_dbg("len=0x%lx, flags=0x%lx", len, vma->vm_flags);
+
+	if (ctx->owner != current) {
+		dax_dbg("devmap called from wrong thread");
+		return -EINVAL;
+	}
+
+	if (len != DAX_MMAP_LEN) {
+		dax_dbg("len(%lu) != DAX_MMAP_LEN(%d)", len, DAX_MMAP_LEN);
+		return -EINVAL;
+	}
+
+	/* completion area is mapped read-only for user */
+	if (vma->vm_flags & VM_WRITE)
+		return -EPERM;
+	vma->vm_flags &= ~VM_MAYWRITE;
+
+	if (remap_pfn_range(vma, vma->vm_start, ctx->ca_buf_ra >> PAGE_SHIFT,
+			    len, vma->vm_page_prot))
+		return -EAGAIN;
+
+	dax_dbg("mmapped completion area at uva 0x%lx", vma->vm_start);
+	return 0;
+}
+
+/* Unlock user pages. Called during dequeue or device close */
+static void dax_unlock_pages(struct dax_ctx *ctx, int ccb_index, int nelem)
+{
+	int i, j;
+
+	for (i = ccb_index; i < ccb_index + nelem; i++) {
+		for (j = 0; j < NUM_STREAM_TYPES; j++) {
+			struct page *p = ctx->pages[i][j];
+
+			if (p) {
+				dax_dbg("freeing page %p", p);
+				if (j == OUT)
+					set_page_dirty(p);
+				put_page(p);
+				ctx->pages[i][j] = NULL;
+			}
+		}
+	}
+}
+
+static int dax_lock_page(void *va, struct page **p)
+{
+	int ret;
+
+	dax_dbg("uva %p", va);
+
+	ret = get_user_pages_fast((unsigned long)va, 1, 1, p);
+	if (ret == 1) {
+		dax_dbg("locked page %p, for VA %p", *p, va);
+		return 0;
+	}
+
+	dax_dbg("get_user_pages failed, va=%p, ret=%d", va, ret);
+	return -1;
+}
+
+static int dax_lock_pages(struct dax_ctx *ctx, int idx,
+			  int nelem, u64 *err_va)
+{
+	int i;
+
+	for (i = 0; i < nelem; i++) {
+		struct dax_ccb *ccbp = &ctx->ccb_buf[i];
+
+		/*
+		 * For each address in the CCB whose type is virtual,
+		 * lock the page and change the type to virtual alternate
+		 * context. On error, return the offending address in
+		 * err_va.
+		 */
+		if (ccbp->hdr.out_addr_type == DAX_ADDR_TYPE_VA) {
+			dax_dbg("output");
+			if (dax_lock_page(ccbp->out,
+					  &ctx->pages[i + idx][OUT]) != 0) {
+				*err_va = (u64)ccbp->out;
+				goto error;
+			}
+			ccbp->hdr.out_addr_type = DAX_ADDR_TYPE_VA_ALT;
+		}
+
+		if (ccbp->hdr.pri_addr_type == DAX_ADDR_TYPE_VA) {
+			dax_dbg("input");
+			if (dax_lock_page(ccbp->pri,
+					  &ctx->pages[i + idx][PRI]) != 0) {
+				*err_va = (u64)ccbp->pri;
+				goto error;
+			}
+			ccbp->hdr.pri_addr_type = DAX_ADDR_TYPE_VA_ALT;
+		}
+
+		if (ccbp->hdr.sec_addr_type == DAX_ADDR_TYPE_VA) {
+			dax_dbg("sec input");
+			if (dax_lock_page(ccbp->sec,
+					  &ctx->pages[i + idx][SEC]) != 0) {
+				*err_va = (u64)ccbp->sec;
+				goto error;
+			}
+			ccbp->hdr.sec_addr_type = DAX_ADDR_TYPE_VA_ALT;
+		}
+
+		if (ccbp->hdr.table_addr_type == DAX_ADDR_TYPE_VA) {
+			dax_dbg("tbl");
+			if (dax_lock_page(ccbp->tbl,
+					  &ctx->pages[i + idx][TBL]) != 0) {
+				*err_va = (u64)ccbp->tbl;
+				goto error;
+			}
+			ccbp->hdr.table_addr_type = DAX_ADDR_TYPE_VA_ALT;
+		}
+
+		/* skip over 2nd 64 bytes of long CCB */
+		if (ccbp->hdr.longccb)
+			i++;
+	}
+	return DAX_SUBMIT_OK;
+
+error:
+	dax_unlock_pages(ctx, idx, nelem);
+	return DAX_SUBMIT_ERR_NOACCESS;
+}
+
+static void dax_ccb_wait(struct dax_ctx *ctx, int idx)
+{
+	int ret, nretries;
+	u16 kill_res;
+
+	dax_dbg("idx=%d", idx);
+
+	for (nretries = 0; nretries < DAX_CCB_RETRIES; nretries++) {
+		if (ctx->ca_buf[idx].status == CCA_STAT_NOT_COMPLETED)
+			udelay(DAX_CCB_USEC);
+		else
+			return;
+	}
+	dax_dbg("ctx (%p): CCB[%d] timed out, wait usec=%d, retries=%d. Killing ccb",
+		(void *)ctx, idx, DAX_CCB_USEC, DAX_CCB_RETRIES);
+
+	ret = dax_ccb_kill(ctx->ca_buf_ra + idx * sizeof(struct dax_cca),
+			   &kill_res);
+	dax_dbg("Kill CCB[%d] %s", idx, ret ? "failed" : "succeeded");
+}
+
+static int dax_close(struct inode *ino, struct file *f)
+{
+	struct dax_ctx *ctx = (struct dax_ctx *)f->private_data;
+	int i;
+
+	f->private_data = NULL;
+
+	for (i = 0; i < DAX_CA_ELEMS; i++) {
+		if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) {
+			dax_dbg("CCB[%d] not completed", i);
+			dax_ccb_wait(ctx, i);
+		}
+		dax_unlock_pages(ctx, i, 1);
+	}
+
+	kfree(ctx->ccb_buf);
+	kfree(ctx->ca_buf);
+	dax_stat_dbg("CCBs: %d good, %d bad", ctx->ccb_count, ctx->fail_count);
+	kfree(ctx);
+
+	return 0;
+}
+
+static ssize_t dax_read(struct file *f, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct dax_ctx *ctx = f->private_data;
+
+	if (ctx->client != current)
+		return -EUSERS;
+
+	ctx->client = NULL;
+
+	if (count != sizeof(union ccb_result))
+		return -EINVAL;
+	if (copy_to_user(buf, &ctx->result, sizeof(union ccb_result)))
+		return -EFAULT;
+	return count;
+}
+
+static ssize_t dax_write(struct file *f, const char __user *buf,
+			 size_t count, loff_t *ppos)
+{
+	struct dax_ctx *ctx = f->private_data;
+	struct dax_command hdr;
+	unsigned long ca;
+	int i, idx, ret;
+
+	if (ctx->client != NULL)
+		return -EINVAL;
+
+	if (count == 0 || count > DAX_MAX_CCBS * sizeof(struct dax_ccb))
+		return -EINVAL;
+
+	if (count % sizeof(struct dax_ccb) == 0)
+		return dax_ccb_exec(ctx, buf, count, ppos); /* CCB EXEC */
+
+	if (count != sizeof(struct dax_command))
+		return -EINVAL;
+
+	/* immediate command */
+	if (ctx->owner != current)
+		return -EUSERS;
+
+	if (copy_from_user(&hdr, buf, sizeof(hdr)))
+		return -EFAULT;
+
+	ca = ctx->ca_buf_ra + hdr.ca_offset;
+
+	switch (hdr.command) {
+	case CCB_KILL:
+		if (hdr.ca_offset >= DAX_MMAP_LEN) {
+			dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)",
+				hdr.ca_offset, DAX_MMAP_LEN);
+			return -EINVAL;
+		}
+
+		ret = dax_ccb_kill(ca, &ctx->result.kill.action);
+		if (ret != 0) {
+			dax_dbg("dax_ccb_kill failed (ret=%d)", ret);
+			return ret;
+		}
+
+		dax_info_dbg("killed (ca_offset %d)", hdr.ca_offset);
+		idx = hdr.ca_offset / sizeof(struct dax_cca);
+		ctx->ca_buf[idx].status = CCA_STAT_KILLED;
+		ctx->ca_buf[idx].err = CCA_ERR_KILLED;
+		ctx->client = current;
+		return count;
+
+	case CCB_INFO:
+		if (hdr.ca_offset >= DAX_MMAP_LEN) {
+			dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)",
+				hdr.ca_offset, DAX_MMAP_LEN);
+			return -EINVAL;
+		}
+
+		ret = dax_ccb_info(ca, &ctx->result.info);
+		if (ret != 0) {
+			dax_dbg("dax_ccb_info failed (ret=%d)", ret);
+			return ret;
+		}
+
+		dax_info_dbg("info succeeded on ca_offset %d", hdr.ca_offset);
+		ctx->client = current;
+		return count;
+
+	case CCB_DEQUEUE:
+		for (i = 0; i < DAX_CA_ELEMS; i++) {
+			if (ctx->ca_buf[i].status !=
+			    CCA_STAT_NOT_COMPLETED)
+				dax_unlock_pages(ctx, i, 1);
+		}
+		return count;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dax_open(struct inode *inode, struct file *f)
+{
+	struct dax_ctx *ctx = NULL;
+	int i;
+
+	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+	if (ctx == NULL)
+		goto done;
+
+	ctx->ccb_buf = kcalloc(DAX_MAX_CCBS, sizeof(struct dax_ccb),
+			       GFP_KERNEL);
+	if (ctx->ccb_buf == NULL)
+		goto done;
+
+	ctx->ccb_buf_ra = virt_to_phys(ctx->ccb_buf);
+	dax_dbg("ctx->ccb_buf=0x%p, ccb_buf_ra=0x%llx",
+		(void *)ctx->ccb_buf, ctx->ccb_buf_ra);
+
+	/* allocate CCB completion area buffer */
+	ctx->ca_buf = kzalloc(DAX_MMAP_LEN, GFP_KERNEL);
+	if (ctx->ca_buf == NULL)
+		goto alloc_error;
+	for (i = 0; i < DAX_CA_ELEMS; i++)
+		ctx->ca_buf[i].status = CCA_STAT_COMPLETED;
+
+	ctx->ca_buf_ra = virt_to_phys(ctx->ca_buf);
+	dax_dbg("ctx=0x%p, ctx->ca_buf=0x%p, ca_buf_ra=0x%llx",
+		(void *)ctx, (void *)ctx->ca_buf, ctx->ca_buf_ra);
+
+	ctx->owner = current;
+	f->private_data = ctx;
+	return 0;
+
+alloc_error:
+	kfree(ctx->ccb_buf);
+done:
+	kfree(ctx);
+	return -ENOMEM;
+}
+
+static char *dax_hv_errno(unsigned long hv_ret, int *ret)
+{
+	switch (hv_ret) {
+	case HV_EBADALIGN:
+		*ret = -EFAULT;
+		return "HV_EBADALIGN";
+	case HV_ENORADDR:
+		*ret = -EFAULT;
+		return "HV_ENORADDR";
+	case HV_EINVAL:
+		*ret = -EINVAL;
+		return "HV_EINVAL";
+	case HV_EWOULDBLOCK:
+		*ret = -EAGAIN;
+		return "HV_EWOULDBLOCK";
+	case HV_ENOACCESS:
+		*ret = -EPERM;
+		return "HV_ENOACCESS";
+	default:
+		break;
+	}
+
+	*ret = -EIO;
+	return "UNKNOWN";
+}
+
+static int dax_ccb_kill(u64 ca, u16 *kill_res)
+{
+	unsigned long hv_ret;
+	int count, ret = 0;
+	char *err_str;
+
+	for (count = 0; count < DAX_CCB_RETRIES; count++) {
+		dax_dbg("attempting kill on ca_ra 0x%llx", ca);
+		hv_ret = sun4v_ccb_kill(ca, kill_res);
+
+		if (hv_ret == HV_EOK) {
+			dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca,
+				     *kill_res);
+		} else {
+			err_str = dax_hv_errno(hv_ret, &ret);
+			dax_dbg("%s (ca_ra 0x%llx)", err_str, ca);
+		}
+
+		if (ret != -EAGAIN)
+			return ret;
+		dax_info_dbg("ccb_kill count = %d", count);
+		udelay(DAX_CCB_USEC);
+	}
+
+	return -EAGAIN;
+}
+
+static int dax_ccb_info(u64 ca, struct ccb_info_result *info)
+{
+	unsigned long hv_ret;
+	char *err_str;
+	int ret = 0;
+
+	dax_dbg("attempting info on ca_ra 0x%llx", ca);
+	hv_ret = sun4v_ccb_info(ca, info);
+
+	if (hv_ret == HV_EOK) {
+		dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca, info->state);
+		if (info->state == DAX_CCB_ENQUEUED) {
+			dax_info_dbg("dax_unit %d, queue_num %d, queue_pos %d",
+				     info->inst_num, info->q_num, info->q_pos);
+		}
+	} else {
+		err_str = dax_hv_errno(hv_ret, &ret);
+		dax_dbg("%s (ca_ra 0x%llx)", err_str, ca);
+	}
+
+	return ret;
+}
+
+static void dax_prt_ccbs(struct dax_ccb *ccb, int nelem)
+{
+	int i, j;
+	u64 *ccbp;
+
+	dax_dbg("ccb buffer:");
+	for (i = 0; i < nelem; i++) {
+		ccbp = (u64 *)&ccb[i];
+		dax_dbg(" %sccb[%d]", ccb[i].hdr.longccb ? "long " : "",  i);
+		for (j = 0; j < 8; j++)
+			dax_dbg("\tccb[%d].dwords[%d]=0x%llx",
+				i, j, *(ccbp + j));
+	}
+}
+
+/*
+ * Validates user CCB content.  Also sets completion address and address types
+ * for all addresses contained in CCB.
+ */
+static int dax_preprocess_usr_ccbs(struct dax_ctx *ctx, int idx, int nelem)
+{
+	int i;
+
+	/*
+	 * The user is not allowed to specify real address types in
+	 * the CCB header.  This must be enforced by the kernel before
+	 * submitting the CCBs to HV.  The only allowed values for all
+	 * address fields are VA or IMM
+	 */
+	for (i = 0; i < nelem; i++) {
+		struct dax_ccb *ccbp = &ctx->ccb_buf[i];
+		unsigned long ca_offset;
+
+		if (ccbp->hdr.ccb_version > max_ccb_version)
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+
+		switch (ccbp->hdr.opcode) {
+		case DAX_OP_SYNC_NOP:
+		case DAX_OP_EXTRACT:
+		case DAX_OP_SCAN_VALUE:
+		case DAX_OP_SCAN_RANGE:
+		case DAX_OP_TRANSLATE:
+		case DAX_OP_SCAN_VALUE | DAX_OP_INVERT:
+		case DAX_OP_SCAN_RANGE | DAX_OP_INVERT:
+		case DAX_OP_TRANSLATE | DAX_OP_INVERT:
+		case DAX_OP_SELECT:
+			break;
+		default:
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+		}
+
+		if (ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_VA &&
+		    ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_NONE) {
+			dax_dbg("invalid out_addr_type in user CCB[%d]", i);
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+		}
+
+		if (ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_VA &&
+		    ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_NONE) {
+			dax_dbg("invalid pri_addr_type in user CCB[%d]", i);
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+		}
+
+		if (ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_VA &&
+		    ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_NONE) {
+			dax_dbg("invalid sec_addr_type in user CCB[%d]", i);
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+		}
+
+		if (ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_VA &&
+		    ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_NONE) {
+			dax_dbg("invalid table_addr_type in user CCB[%d]", i);
+			return DAX_SUBMIT_ERR_CCB_INVAL;
+		}
+
+		/* set completion (real) address and address type */
+		ccbp->hdr.cca_addr_type = DAX_ADDR_TYPE_RA;
+		ca_offset = (idx + i) * sizeof(struct dax_cca);
+		ccbp->ca = (void *)ctx->ca_buf_ra + ca_offset;
+		memset(&ctx->ca_buf[idx + i], 0, sizeof(struct dax_cca));
+
+		dax_dbg("ccb[%d]=%p, ca_offset=0x%lx, compl RA=0x%llx",
+			i, ccbp, ca_offset, ctx->ca_buf_ra + ca_offset);
+
+		/* skip over 2nd 64 bytes of long CCB */
+		if (ccbp->hdr.longccb)
+			i++;
+	}
+
+	return DAX_SUBMIT_OK;
+}
+
+static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	unsigned long accepted_len, hv_rv;
+	int i, idx, nccbs, naccepted;
+
+	ctx->client = current;
+	idx = *ppos;
+	nccbs = count / sizeof(struct dax_ccb);
+
+	if (ctx->owner != current) {
+		dax_dbg("wrong thread");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_THR_INIT;
+		return 0;
+	}
+	dax_dbg("args: ccb_buf_len=%ld, idx=%d", count, idx);
+
+	/* for given index and length, verify ca_buf range exists */
+	if (idx < 0 || idx > (DAX_CA_ELEMS - nccbs)) {
+		ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL;
+		return 0;
+	}
+
+	/*
+	 * Copy CCBs into kernel buffer to prevent modification by the
+	 * user in between validation and submission.
+	 */
+	if (copy_from_user(ctx->ccb_buf, buf, count)) {
+		dax_dbg("copyin of user CCB buffer failed");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_ARR_MMU_MISS;
+		return 0;
+	}
+
+	/* check to see if ca_buf[idx] .. ca_buf[idx + nccbs] are available */
+	for (i = idx; i < idx + nccbs; i++) {
+		if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) {
+			dax_dbg("CA range not available, dequeue needed");
+			ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL;
+			return 0;
+		}
+	}
+	dax_unlock_pages(ctx, idx, nccbs);
+
+	ctx->result.exec.status = dax_preprocess_usr_ccbs(ctx, idx, nccbs);
+	if (ctx->result.exec.status != DAX_SUBMIT_OK)
+		return 0;
+
+	ctx->result.exec.status = dax_lock_pages(ctx, idx, nccbs,
+						 &ctx->result.exec.status_data);
+	if (ctx->result.exec.status != DAX_SUBMIT_OK)
+		return 0;
+
+	if (dax_debug & DAX_DBG_FLG_BASIC)
+		dax_prt_ccbs(ctx->ccb_buf, nccbs);
+
+	hv_rv = sun4v_ccb_submit(ctx->ccb_buf_ra, count,
+				 HV_CCB_QUERY_CMD | HV_CCB_VA_SECONDARY, 0,
+				 &accepted_len, &ctx->result.exec.status_data);
+
+	switch (hv_rv) {
+	case HV_EOK:
+		/*
+		 * Hcall succeeded with no errors but the accepted
+		 * length may be less than the requested length.  The
+		 * only way the driver can resubmit the remainder is
+		 * to wait for completion of the submitted CCBs since
+		 * there is no way to guarantee the ordering semantics
+		 * required by the client applications.  Therefore we
+		 * let the user library deal with resubmissions.
+		 */
+		ctx->result.exec.status = DAX_SUBMIT_OK;
+		break;
+	case HV_EWOULDBLOCK:
+		/*
+		 * This is a transient HV API error. The user library
+		 * can retry.
+		 */
+		dax_dbg("hcall returned HV_EWOULDBLOCK");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_WOULDBLOCK;
+		break;
+	case HV_ENOMAP:
+		/*
+		 * HV was unable to translate a VA. The VA it could
+		 * not translate is returned in the status_data param.
+		 */
+		dax_dbg("hcall returned HV_ENOMAP");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_NOMAP;
+		break;
+	case HV_EINVAL:
+		/*
+		 * This is the result of an invalid user CCB as HV is
+		 * validating some of the user CCB fields.  Pass this
+		 * error back to the user. There is no supporting info
+		 * to isolate the invalid field.
+		 */
+		dax_dbg("hcall returned HV_EINVAL");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_INVAL;
+		break;
+	case HV_ENOACCESS:
+		/*
+		 * HV found a VA that did not have the appropriate
+		 * permissions (such as the w bit). The VA in question
+		 * is returned in status_data param.
+		 */
+		dax_dbg("hcall returned HV_ENOACCESS");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_NOACCESS;
+		break;
+	case HV_EUNAVAILABLE:
+		/*
+		 * The requested CCB operation could not be performed
+		 * at this time. Return the specific unavailable code
+		 * in the status_data field.
+		 */
+		dax_dbg("hcall returned HV_EUNAVAILABLE");
+		ctx->result.exec.status = DAX_SUBMIT_ERR_UNAVAIL;
+		break;
+	default:
+		ctx->result.exec.status = DAX_SUBMIT_ERR_INTERNAL;
+		dax_dbg("unknown hcall return value (%ld)", hv_rv);
+		break;
+	}
+
+	/* unlock pages associated with the unaccepted CCBs */
+	naccepted = accepted_len / sizeof(struct dax_ccb);
+	dax_unlock_pages(ctx, idx + naccepted, nccbs - naccepted);
+
+	/* mark unaccepted CCBs as not completed */
+	for (i = idx + naccepted; i < idx + nccbs; i++)
+		ctx->ca_buf[i].status = CCA_STAT_COMPLETED;
+
+	ctx->ccb_count += naccepted;
+	ctx->fail_count += nccbs - naccepted;
+
+	dax_dbg("hcall rv=%ld, accepted_len=%ld, status_data=0x%llx, ret status=%d",
+		hv_rv, accepted_len, ctx->result.exec.status_data,
+		ctx->result.exec.status);
+
+	if (count == accepted_len)
+		ctx->client = NULL; /* no read needed to complete protocol */
+	return accepted_len;
+}
diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c
new file mode 100644
index 0000000..0a50133
--- /dev/null
+++ b/drivers/sbus/char/uctrl.c
@@ -0,0 +1,437 @@
+/* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3
+ *
+ * Copyright 1999 Derrick J Brashear (shadow@dementia.org)
+ * Copyright 2008 David S. Miller (davem@davemloft.net)
+ */
+
+#include <linux/module.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/ioport.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+
+#include <asm/openprom.h>
+#include <asm/oplib.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <asm/pgtable.h>
+
+#define UCTRL_MINOR	174
+
+#define DEBUG 1
+#ifdef DEBUG
+#define dprintk(x) printk x
+#else
+#define dprintk(x)
+#endif
+
+struct uctrl_regs {
+	u32 uctrl_intr;
+	u32 uctrl_data;
+	u32 uctrl_stat;
+	u32 uctrl_xxx[5];
+};
+
+struct ts102_regs {
+	u32 card_a_intr;
+	u32 card_a_stat;
+	u32 card_a_ctrl;
+	u32 card_a_xxx;
+	u32 card_b_intr;
+	u32 card_b_stat;
+	u32 card_b_ctrl;
+	u32 card_b_xxx;
+	u32 uctrl_intr;
+	u32 uctrl_data;
+	u32 uctrl_stat;
+	u32 uctrl_xxx;
+	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 DEFINE_MUTEX(uctrl_mutex);
+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,
+};
+
+static struct uctrl_driver {
+	struct uctrl_regs __iomem *regs;
+	int irq;
+	int pending;
+	struct uctrl_status status;
+} *global_driver;
+
+static void uctrl_get_event_status(struct uctrl_driver *);
+static void uctrl_get_external_status(struct uctrl_driver *);
+
+static long
+uctrl_ioctl(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)
+{
+	mutex_lock(&uctrl_mutex);
+	uctrl_get_event_status(global_driver);
+	uctrl_get_external_status(global_driver);
+	mutex_unlock(&uctrl_mutex);
+	return 0;
+}
+
+static irqreturn_t uctrl_interrupt(int irq, void *dev_id)
+{
+	return IRQ_HANDLED;
+}
+
+static const struct file_operations uctrl_fops = {
+	.owner =	THIS_MODULE,
+	.llseek =	no_llseek,
+	.unlocked_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 & sbus_readl(&driver->regs->uctrl_stat)) \
+      break; \
+  } \
+  dprintk(("write data 0x%02x\n", value)); \
+  sbus_writel(value, &driver->regs->uctrl_data); \
+}
+
+/* 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 & sbus_readl(&driver->regs->uctrl_stat)) == 0) \
+      break; \
+    udelay(1); \
+  } \
+  value = sbus_readl(&driver->regs->uctrl_data); \
+  dprintk(("read data 0x%02x\n", value)); \
+  sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \
+}
+
+static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn)
+{
+	int stat, incnt, outcnt, bytecnt, intr;
+	u32 byte;
+
+	stat = sbus_readl(&driver->regs->uctrl_stat);
+	intr = sbus_readl(&driver->regs->uctrl_intr);
+	sbus_writel(stat, &driver->regs->uctrl_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++;
+	}
+}
+
+static void uctrl_get_event_status(struct uctrl_driver *driver)
+{
+	struct uctrl_txn txn;
+	u8 outbits[2];
+
+	txn.opcode = READ_EVENT_STATUS;
+	txn.inbits = 0;
+	txn.outbits = 2;
+	txn.inbuf = NULL;
+	txn.outbuf = outbits;
+
+	uctrl_do_txn(driver, &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));
+}
+
+static void uctrl_get_external_status(struct uctrl_driver *driver)
+{
+	struct uctrl_txn txn;
+	u8 outbits[2];
+	int i, v;
+
+	txn.opcode = READ_EXTERNAL_STATUS;
+	txn.inbits = 0;
+	txn.outbits = 2;
+	txn.inbuf = NULL;
+	txn.outbuf = outbits;
+
+	uctrl_do_txn(driver, &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 uctrl_probe(struct platform_device *op)
+{
+	struct uctrl_driver *p;
+	int err = -ENOMEM;
+
+	p = kzalloc(sizeof(*p), GFP_KERNEL);
+	if (!p) {
+		printk(KERN_ERR "uctrl: Unable to allocate device struct.\n");
+		goto out;
+	}
+
+	p->regs = of_ioremap(&op->resource[0], 0,
+			     resource_size(&op->resource[0]),
+			     "uctrl");
+	if (!p->regs) {
+		printk(KERN_ERR "uctrl: Unable to map registers.\n");
+		goto out_free;
+	}
+
+	p->irq = op->archdata.irqs[0];
+	err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p);
+	if (err) {
+		printk(KERN_ERR "uctrl: Unable to register irq.\n");
+		goto out_iounmap;
+	}
+
+	err = misc_register(&uctrl_dev);
+	if (err) {
+		printk(KERN_ERR "uctrl: Unable to register misc device.\n");
+		goto out_free_irq;
+	}
+
+	sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr);
+	printk(KERN_INFO "%pOF: uctrl regs[0x%p] (irq %d)\n",
+	       op->dev.of_node, p->regs, p->irq);
+	uctrl_get_event_status(p);
+	uctrl_get_external_status(p);
+
+	dev_set_drvdata(&op->dev, p);
+	global_driver = p;
+
+out:
+	return err;
+
+out_free_irq:
+	free_irq(p->irq, p);
+
+out_iounmap:
+	of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
+
+out_free:
+	kfree(p);
+	goto out;
+}
+
+static int uctrl_remove(struct platform_device *op)
+{
+	struct uctrl_driver *p = dev_get_drvdata(&op->dev);
+
+	if (p) {
+		misc_deregister(&uctrl_dev);
+		free_irq(p->irq, p);
+		of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0]));
+		kfree(p);
+	}
+	return 0;
+}
+
+static const struct of_device_id uctrl_match[] = {
+	{
+		.name = "uctrl",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, uctrl_match);
+
+static struct platform_driver uctrl_driver = {
+	.driver = {
+		.name = "uctrl",
+		.of_match_table = uctrl_match,
+	},
+	.probe		= uctrl_probe,
+	.remove		= uctrl_remove,
+};
+
+
+module_platform_driver(uctrl_driver);
+
+MODULE_LICENSE("GPL");