summaryrefslogtreecommitdiff
path: root/drivers/sbus/char
diff options
context:
space:
mode:
authorAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
committerAndré Fabian Silva Delgado <emulatorman@parabola.nu>2015-08-05 17:04:01 -0300
commit57f0f512b273f60d52568b8c6b77e17f5636edc0 (patch)
tree5e910f0e82173f4ef4f51111366a3f1299037a7b /drivers/sbus/char
Initial import
Diffstat (limited to 'drivers/sbus/char')
-rw-r--r--drivers/sbus/char/Kconfig74
-rw-r--r--drivers/sbus/char/Makefile18
-rw-r--r--drivers/sbus/char/bbc_envctrl.c600
-rw-r--r--drivers/sbus/char/bbc_i2c.c424
-rw-r--r--drivers/sbus/char/bbc_i2c.h85
-rw-r--r--drivers/sbus/char/display7seg.c272
-rw-r--r--drivers/sbus/char/envctrl.c1136
-rw-r--r--drivers/sbus/char/flash.c218
-rw-r--r--drivers/sbus/char/jsflash.c635
-rw-r--r--drivers/sbus/char/max1617.h27
-rw-r--r--drivers/sbus/char/openprom.c762
-rw-r--r--drivers/sbus/char/uctrl.c437
12 files changed, 4688 insertions, 0 deletions
diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig
new file mode 100644
index 000000000..5ba684f73
--- /dev/null
+++ b/drivers/sbus/char/Kconfig
@@ -0,0 +1,74 @@
+
+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 SUN_JSFLASH
+ tristate "JavaStation OS Flash SIMM"
+ depends on SPARC32
+ help
+ If you say Y here, you will be able to boot from your JavaStation's
+ Flash memory.
+
+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.
+
+endmenu
+
diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile
new file mode 100644
index 000000000..78b6183c9
--- /dev/null
+++ b/drivers/sbus/char/Makefile
@@ -0,0 +1,18 @@
+#
+# 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_SUN_JSFLASH) += jsflash.o
+obj-$(CONFIG_BBC_I2C) += bbc.o
diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c
new file mode 100644
index 000000000..228c782d6
--- /dev/null
+++ b/drivers/sbus/char/bbc_envctrl.c
@@ -0,0 +1,600 @@
+/* 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 000000000..129967ad3
--- /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 000000000..4b4531066
--- /dev/null
+++ b/drivers/sbus/char/bbc_i2c.h
@@ -0,0 +1,85 @@
+#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 000000000..33fbe8249
--- /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 <asm/uaccess.h> /* put_/get_user */
+#include <asm/io.h>
+
+#include <asm/display7seg.h>
+
+#define D7S_MINOR 193
+#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%s at [%s:0x%llx] %s\n",
+ op->dev.of_node->full_name,
+ (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;
+
+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 000000000..5609b602c
--- /dev/null
+++ b/drivers/sbus/char/envctrl.c
@@ -0,0 +1,1136 @@
+/* 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 <asm/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;
+ }
+ return;
+ }
+ }
+
+ /* 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 000000000..206ef4232
--- /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 <asm/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 "%s: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n",
+ op->dev.of_node->full_name,
+ 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/jsflash.c b/drivers/sbus/char/jsflash.c
new file mode 100644
index 000000000..a40ee1e37
--- /dev/null
+++ b/drivers/sbus/char/jsflash.c
@@ -0,0 +1,635 @@
+/*
+ * drivers/sbus/char/jsflash.c
+ *
+ * Copyright (C) 1991, 1992 Linus Torvalds (drivers/char/mem.c)
+ * Copyright (C) 1997 Eddie C. Dost (drivers/sbus/char/flash.c)
+ * Copyright (C) 1997-2000 Pavel Machek <pavel@ucw.cz> (drivers/block/nbd.c)
+ * Copyright (C) 1999-2000 Pete Zaitcev
+ *
+ * This driver is used to program OS into a Flash SIMM on
+ * Krups and Espresso platforms.
+ *
+ * TODO: do not allow erase/programming if file systems are mounted.
+ * TODO: Erase/program both banks of a 8MB SIMM.
+ *
+ * It is anticipated that programming an OS Flash will be a routine
+ * procedure. In the same time it is exceedingly dangerous because
+ * a user can program its OBP flash with OS image and effectively
+ * kill the machine.
+ *
+ * This driver uses an interface different from Eddie's flash.c
+ * as a silly safeguard.
+ *
+ * XXX The flash.c manipulates page caching characteristics in a certain
+ * dubious way; also it assumes that remap_pfn_range() can remap
+ * PCI bus locations, which may be false. ioremap() must be used
+ * instead. We should discuss this.
+ */
+
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/miscdevice.h>
+#include <linux/fcntl.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/genhd.h>
+#include <linux/blkdev.h>
+#include <asm/uaccess.h>
+#include <asm/pgtable.h>
+#include <asm/io.h>
+#include <asm/pcic.h>
+#include <asm/oplib.h>
+
+#include <asm/jsflash.h> /* ioctl arguments. <linux/> ?? */
+#define JSFIDSZ (sizeof(struct jsflash_ident_arg))
+#define JSFPRGSZ (sizeof(struct jsflash_program_arg))
+
+/*
+ * Our device numbers have no business in system headers.
+ * The only thing a user knows is the device name /dev/jsflash.
+ *
+ * Block devices are laid out like this:
+ * minor+0 - Bootstrap, for 8MB SIMM 0x20400000[0x800000]
+ * minor+1 - Filesystem to mount, normally 0x20400400[0x7ffc00]
+ * minor+2 - Whole flash area for any case... 0x20000000[0x01000000]
+ * Total 3 minors per flash device.
+ *
+ * It is easier to have static size vectors, so we define
+ * a total minor range JSF_MAX, which must cover all minors.
+ */
+/* character device */
+#define JSF_MINOR 178 /* 178 is registered with hpa */
+/* block device */
+#define JSF_MAX 3 /* 3 minors wasted total so far. */
+#define JSF_NPART 3 /* 3 minors per flash device */
+#define JSF_PART_BITS 2 /* 2 bits of minors to cover JSF_NPART */
+#define JSF_PART_MASK 0x3 /* 2 bits mask */
+
+static DEFINE_MUTEX(jsf_mutex);
+
+/*
+ * Access functions.
+ * We could ioremap(), but it's easier this way.
+ */
+static unsigned int jsf_inl(unsigned long addr)
+{
+ unsigned long retval;
+
+ __asm__ __volatile__("lda [%1] %2, %0\n\t" :
+ "=r" (retval) :
+ "r" (addr), "i" (ASI_M_BYPASS));
+ return retval;
+}
+
+static void jsf_outl(unsigned long addr, __u32 data)
+{
+
+ __asm__ __volatile__("sta %0, [%1] %2\n\t" : :
+ "r" (data), "r" (addr), "i" (ASI_M_BYPASS) :
+ "memory");
+}
+
+/*
+ * soft carrier
+ */
+
+struct jsfd_part {
+ unsigned long dbase;
+ unsigned long dsize;
+};
+
+struct jsflash {
+ unsigned long base;
+ unsigned long size;
+ unsigned long busy; /* In use? */
+ struct jsflash_ident_arg id;
+ /* int mbase; */ /* Minor base, typically zero */
+ struct jsfd_part dv[JSF_NPART];
+};
+
+/*
+ * We do not map normal memory or obio as a safety precaution.
+ * But offsets are real, for ease of userland programming.
+ */
+#define JSF_BASE_TOP 0x30000000
+#define JSF_BASE_ALL 0x20000000
+
+#define JSF_BASE_JK 0x20400000
+
+/*
+ */
+static struct gendisk *jsfd_disk[JSF_MAX];
+
+/*
+ * Let's pretend we may have several of these...
+ */
+static struct jsflash jsf0;
+
+/*
+ * Wait for AMD to finish its embedded algorithm.
+ * We use the Toggle bit DQ6 (0x40) because it does not
+ * depend on the data value as /DATA bit DQ7 does.
+ *
+ * XXX Do we need any timeout here? So far it never hanged, beware broken hw.
+ */
+static void jsf_wait(unsigned long p) {
+ unsigned int x1, x2;
+
+ for (;;) {
+ x1 = jsf_inl(p);
+ x2 = jsf_inl(p);
+ if ((x1 & 0x40404040) == (x2 & 0x40404040)) return;
+ }
+}
+
+/*
+ * Programming will only work if Flash is clean,
+ * we leave it to the programmer application.
+ *
+ * AMD must be programmed one byte at a time;
+ * thus, Simple Tech SIMM must be written 4 bytes at a time.
+ *
+ * Write waits for the chip to become ready after the write
+ * was finished. This is done so that application would read
+ * consistent data after the write is done.
+ */
+static void jsf_write4(unsigned long fa, u32 data) {
+
+ jsf_outl(fa, 0xAAAAAAAA); /* Unlock 1 Write 1 */
+ jsf_outl(fa, 0x55555555); /* Unlock 1 Write 2 */
+ jsf_outl(fa, 0xA0A0A0A0); /* Byte Program */
+ jsf_outl(fa, data);
+
+ jsf_wait(fa);
+}
+
+/*
+ */
+static void jsfd_read(char *buf, unsigned long p, size_t togo) {
+ union byte4 {
+ char s[4];
+ unsigned int n;
+ } b;
+
+ while (togo >= 4) {
+ togo -= 4;
+ b.n = jsf_inl(p);
+ memcpy(buf, b.s, 4);
+ p += 4;
+ buf += 4;
+ }
+}
+
+static void jsfd_do_request(struct request_queue *q)
+{
+ struct request *req;
+
+ req = blk_fetch_request(q);
+ while (req) {
+ struct jsfd_part *jdp = req->rq_disk->private_data;
+ unsigned long offset = blk_rq_pos(req) << 9;
+ size_t len = blk_rq_cur_bytes(req);
+ int err = -EIO;
+
+ if ((offset + len) > jdp->dsize)
+ goto end;
+
+ if (rq_data_dir(req) != READ) {
+ printk(KERN_ERR "jsfd: write\n");
+ goto end;
+ }
+
+ if ((jdp->dbase & 0xff000000) != 0x20000000) {
+ printk(KERN_ERR "jsfd: bad base %x\n", (int)jdp->dbase);
+ goto end;
+ }
+
+ jsfd_read(bio_data(req->bio), jdp->dbase + offset, len);
+ err = 0;
+ end:
+ if (!__blk_end_request_cur(req, err))
+ req = blk_fetch_request(q);
+ }
+}
+
+/*
+ * The memory devices use the full 32/64 bits of the offset, and so we cannot
+ * check against negative addresses: they are ok. The return value is weird,
+ * though, in that case (0).
+ *
+ * also note that seeking relative to the "end of file" isn't supported:
+ * it has no meaning, so it returns -EINVAL.
+ */
+static loff_t jsf_lseek(struct file * file, loff_t offset, int orig)
+{
+ loff_t ret;
+
+ mutex_lock(&jsf_mutex);
+ switch (orig) {
+ case 0:
+ file->f_pos = offset;
+ ret = file->f_pos;
+ break;
+ case 1:
+ file->f_pos += offset;
+ ret = file->f_pos;
+ break;
+ default:
+ ret = -EINVAL;
+ }
+ mutex_unlock(&jsf_mutex);
+ return ret;
+}
+
+/*
+ * OS SIMM Cannot be read in other size but a 32bits word.
+ */
+static ssize_t jsf_read(struct file * file, char __user * buf,
+ size_t togo, loff_t *ppos)
+{
+ unsigned long p = *ppos;
+ char __user *tmp = buf;
+
+ union byte4 {
+ char s[4];
+ unsigned int n;
+ } b;
+
+ if (p < JSF_BASE_ALL || p >= JSF_BASE_TOP) {
+ return 0;
+ }
+
+ if ((p + togo) < p /* wrap */
+ || (p + togo) >= JSF_BASE_TOP) {
+ togo = JSF_BASE_TOP - p;
+ }
+
+ if (p < JSF_BASE_ALL && togo != 0) {
+#if 0 /* __bzero XXX */
+ size_t x = JSF_BASE_ALL - p;
+ if (x > togo) x = togo;
+ clear_user(tmp, x);
+ tmp += x;
+ p += x;
+ togo -= x;
+#else
+ /*
+ * Implementation of clear_user() calls __bzero
+ * without regard to modversions,
+ * so we cannot build a module.
+ */
+ return 0;
+#endif
+ }
+
+ while (togo >= 4) {
+ togo -= 4;
+ b.n = jsf_inl(p);
+ if (copy_to_user(tmp, b.s, 4))
+ return -EFAULT;
+ tmp += 4;
+ p += 4;
+ }
+
+ /*
+ * XXX Small togo may remain if 1 byte is ordered.
+ * It would be nice if we did a word size read and unpacked it.
+ */
+
+ *ppos = p;
+ return tmp-buf;
+}
+
+static ssize_t jsf_write(struct file * file, const char __user * buf,
+ size_t count, loff_t *ppos)
+{
+ return -ENOSPC;
+}
+
+/*
+ */
+static int jsf_ioctl_erase(unsigned long arg)
+{
+ unsigned long p;
+
+ /* p = jsf0.base; hits wrong bank */
+ p = 0x20400000;
+
+ jsf_outl(p, 0xAAAAAAAA); /* Unlock 1 Write 1 */
+ jsf_outl(p, 0x55555555); /* Unlock 1 Write 2 */
+ jsf_outl(p, 0x80808080); /* Erase setup */
+ jsf_outl(p, 0xAAAAAAAA); /* Unlock 2 Write 1 */
+ jsf_outl(p, 0x55555555); /* Unlock 2 Write 2 */
+ jsf_outl(p, 0x10101010); /* Chip erase */
+
+#if 0
+ /*
+ * This code is ok, except that counter based timeout
+ * has no place in this world. Let's just drop timeouts...
+ */
+ {
+ int i;
+ __u32 x;
+ for (i = 0; i < 1000000; i++) {
+ x = jsf_inl(p);
+ if ((x & 0x80808080) == 0x80808080) break;
+ }
+ if ((x & 0x80808080) != 0x80808080) {
+ printk("jsf0: erase timeout with 0x%08x\n", x);
+ } else {
+ printk("jsf0: erase done with 0x%08x\n", x);
+ }
+ }
+#else
+ jsf_wait(p);
+#endif
+
+ return 0;
+}
+
+/*
+ * Program a block of flash.
+ * Very simple because we can do it byte by byte anyway.
+ */
+static int jsf_ioctl_program(void __user *arg)
+{
+ struct jsflash_program_arg abuf;
+ char __user *uptr;
+ unsigned long p;
+ unsigned int togo;
+ union {
+ unsigned int n;
+ char s[4];
+ } b;
+
+ if (copy_from_user(&abuf, arg, JSFPRGSZ))
+ return -EFAULT;
+ p = abuf.off;
+ togo = abuf.size;
+ if ((togo & 3) || (p & 3)) return -EINVAL;
+
+ uptr = (char __user *) (unsigned long) abuf.data;
+ while (togo != 0) {
+ togo -= 4;
+ if (copy_from_user(&b.s[0], uptr, 4))
+ return -EFAULT;
+ jsf_write4(p, b.n);
+ p += 4;
+ uptr += 4;
+ }
+
+ return 0;
+}
+
+static long jsf_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
+{
+ mutex_lock(&jsf_mutex);
+ int error = -ENOTTY;
+ void __user *argp = (void __user *)arg;
+
+ if (!capable(CAP_SYS_ADMIN)) {
+ mutex_unlock(&jsf_mutex);
+ return -EPERM;
+ }
+ switch (cmd) {
+ case JSFLASH_IDENT:
+ if (copy_to_user(argp, &jsf0.id, JSFIDSZ)) {
+ mutex_unlock(&jsf_mutex);
+ return -EFAULT;
+ }
+ break;
+ case JSFLASH_ERASE:
+ error = jsf_ioctl_erase(arg);
+ break;
+ case JSFLASH_PROGRAM:
+ error = jsf_ioctl_program(argp);
+ break;
+ }
+
+ mutex_unlock(&jsf_mutex);
+ return error;
+}
+
+static int jsf_mmap(struct file * file, struct vm_area_struct * vma)
+{
+ return -ENXIO;
+}
+
+static int jsf_open(struct inode * inode, struct file * filp)
+{
+ mutex_lock(&jsf_mutex);
+ if (jsf0.base == 0) {
+ mutex_unlock(&jsf_mutex);
+ return -ENXIO;
+ }
+ if (test_and_set_bit(0, (void *)&jsf0.busy) != 0) {
+ mutex_unlock(&jsf_mutex);
+ return -EBUSY;
+ }
+
+ mutex_unlock(&jsf_mutex);
+ return 0; /* XXX What security? */
+}
+
+static int jsf_release(struct inode *inode, struct file *file)
+{
+ jsf0.busy = 0;
+ return 0;
+}
+
+static const struct file_operations jsf_fops = {
+ .owner = THIS_MODULE,
+ .llseek = jsf_lseek,
+ .read = jsf_read,
+ .write = jsf_write,
+ .unlocked_ioctl = jsf_ioctl,
+ .mmap = jsf_mmap,
+ .open = jsf_open,
+ .release = jsf_release,
+};
+
+static struct miscdevice jsf_dev = { JSF_MINOR, "jsflash", &jsf_fops };
+
+static const struct block_device_operations jsfd_fops = {
+ .owner = THIS_MODULE,
+};
+
+static int jsflash_init(void)
+{
+ int rc;
+ struct jsflash *jsf;
+ phandle node;
+ char banner[128];
+ struct linux_prom_registers reg0;
+
+ node = prom_getchild(prom_root_node);
+ node = prom_searchsiblings(node, "flash-memory");
+ if (node != 0 && (s32)node != -1) {
+ if (prom_getproperty(node, "reg",
+ (char *)&reg0, sizeof(reg0)) == -1) {
+ printk("jsflash: no \"reg\" property\n");
+ return -ENXIO;
+ }
+ if (reg0.which_io != 0) {
+ printk("jsflash: bus number nonzero: 0x%x:%x\n",
+ reg0.which_io, reg0.phys_addr);
+ return -ENXIO;
+ }
+ /*
+ * Flash may be somewhere else, for instance on Ebus.
+ * So, don't do the following check for IIep flash space.
+ */
+#if 0
+ if ((reg0.phys_addr >> 24) != 0x20) {
+ printk("jsflash: suspicious address: 0x%x:%x\n",
+ reg0.which_io, reg0.phys_addr);
+ return -ENXIO;
+ }
+#endif
+ if ((int)reg0.reg_size <= 0) {
+ printk("jsflash: bad size 0x%x\n", (int)reg0.reg_size);
+ return -ENXIO;
+ }
+ } else {
+ /* XXX Remove this code once PROLL ID12 got widespread */
+ printk("jsflash: no /flash-memory node, use PROLL >= 12\n");
+ prom_getproperty(prom_root_node, "banner-name", banner, 128);
+ if (strcmp (banner, "JavaStation-NC") != 0 &&
+ strcmp (banner, "JavaStation-E") != 0) {
+ return -ENXIO;
+ }
+ reg0.which_io = 0;
+ reg0.phys_addr = 0x20400000;
+ reg0.reg_size = 0x00800000;
+ }
+
+ /* Let us be really paranoid for modifications to probing code. */
+ if (sparc_cpu_model != sun4m) {
+ /* We must be on sun4m because we use MMU Bypass ASI. */
+ return -ENXIO;
+ }
+
+ if (jsf0.base == 0) {
+ jsf = &jsf0;
+
+ jsf->base = reg0.phys_addr;
+ jsf->size = reg0.reg_size;
+
+ /* XXX Redo the userland interface. */
+ jsf->id.off = JSF_BASE_ALL;
+ jsf->id.size = 0x01000000; /* 16M - all segments */
+ strcpy(jsf->id.name, "Krups_all");
+
+ jsf->dv[0].dbase = jsf->base;
+ jsf->dv[0].dsize = jsf->size;
+ jsf->dv[1].dbase = jsf->base + 1024;
+ jsf->dv[1].dsize = jsf->size - 1024;
+ jsf->dv[2].dbase = JSF_BASE_ALL;
+ jsf->dv[2].dsize = 0x01000000;
+
+ printk("Espresso Flash @0x%lx [%d MB]\n", jsf->base,
+ (int) (jsf->size / (1024*1024)));
+ }
+
+ if ((rc = misc_register(&jsf_dev)) != 0) {
+ printk(KERN_ERR "jsf: unable to get misc minor %d\n",
+ JSF_MINOR);
+ jsf0.base = 0;
+ return rc;
+ }
+
+ return 0;
+}
+
+static struct request_queue *jsf_queue;
+
+static int jsfd_init(void)
+{
+ static DEFINE_SPINLOCK(lock);
+ struct jsflash *jsf;
+ struct jsfd_part *jdp;
+ int err;
+ int i;
+
+ if (jsf0.base == 0)
+ return -ENXIO;
+
+ err = -ENOMEM;
+ for (i = 0; i < JSF_MAX; i++) {
+ struct gendisk *disk = alloc_disk(1);
+ if (!disk)
+ goto out;
+ jsfd_disk[i] = disk;
+ }
+
+ if (register_blkdev(JSFD_MAJOR, "jsfd")) {
+ err = -EIO;
+ goto out;
+ }
+
+ jsf_queue = blk_init_queue(jsfd_do_request, &lock);
+ if (!jsf_queue) {
+ err = -ENOMEM;
+ unregister_blkdev(JSFD_MAJOR, "jsfd");
+ goto out;
+ }
+
+ for (i = 0; i < JSF_MAX; i++) {
+ struct gendisk *disk = jsfd_disk[i];
+ if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+ jsf = &jsf0; /* actually, &jsfv[i >> JSF_PART_BITS] */
+ jdp = &jsf->dv[i&JSF_PART_MASK];
+
+ disk->major = JSFD_MAJOR;
+ disk->first_minor = i;
+ sprintf(disk->disk_name, "jsfd%d", i);
+ disk->fops = &jsfd_fops;
+ set_capacity(disk, jdp->dsize >> 9);
+ disk->private_data = jdp;
+ disk->queue = jsf_queue;
+ add_disk(disk);
+ set_disk_ro(disk, 1);
+ }
+ return 0;
+out:
+ while (i--)
+ put_disk(jsfd_disk[i]);
+ return err;
+}
+
+MODULE_LICENSE("GPL");
+
+static int __init jsflash_init_module(void) {
+ int rc;
+
+ if ((rc = jsflash_init()) == 0) {
+ jsfd_init();
+ return 0;
+ }
+ return rc;
+}
+
+static void __exit jsflash_cleanup_module(void)
+{
+ int i;
+
+ for (i = 0; i < JSF_MAX; i++) {
+ if ((i & JSF_PART_MASK) >= JSF_NPART) continue;
+ del_gendisk(jsfd_disk[i]);
+ put_disk(jsfd_disk[i]);
+ }
+ if (jsf0.busy)
+ printk("jsf0: cleaning busy unit\n");
+ jsf0.base = 0;
+ jsf0.busy = 0;
+
+ misc_deregister(&jsf_dev);
+ unregister_blkdev(JSFD_MAJOR, "jsfd");
+ blk_cleanup_queue(jsf_queue);
+}
+
+module_init(jsflash_init_module);
+module_exit(jsflash_cleanup_module);
diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h
new file mode 100644
index 000000000..cd30819a0
--- /dev/null
+++ b/drivers/sbus/char/max1617.h
@@ -0,0 +1,27 @@
+/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */
+#ifndef _MAX1617_H
+#define _MAX1617_H
+
+#define MAX1617_AMB_TEMP 0x00 /* Ambient temp in C */
+#define MAX1617_CPU_TEMP 0x01 /* Processor die temp in C */
+#define MAX1617_STATUS 0x02 /* Chip status bits */
+
+/* Read-only versions of 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 000000000..5843288f6
--- /dev/null
+++ b/drivers/sbus/char/openprom.c
@@ -0,0 +1,762 @@
+/*
+ * 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 <asm/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_bus_and_slot (((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 int copyin_string(char __user *user, size_t len, char **ptr)
+{
+ char *tmp;
+
+ if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0)
+ return -EINVAL;
+
+ tmp = kmalloc(len + 1, GFP_KERNEL);
+ if (!tmp)
+ return -ENOMEM;
+
+ if (copy_from_user(tmp, user, len)) {
+ kfree(tmp);
+ return -EFAULT;
+ }
+
+ tmp[len] = '\0';
+
+ *ptr = tmp;
+
+ return 0;
+}
+
+/*
+ * NetBSD /dev/openprom ioctl calls.
+ */
+static int 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);
+
+ err = copyin_string(op.op_name, op.op_namelen, &str);
+ if (err)
+ return err;
+
+ 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 err, len;
+
+ if (copy_from_user(&op, argp, sizeof(op)))
+ return -EFAULT;
+
+ dp = get_node(op.op_nodeid, data);
+ if (!dp)
+ return -EINVAL;
+
+ err = copyin_string(op.op_name, op.op_namelen, &str);
+ if (err)
+ return err;
+
+ 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;
+
+ err = copyin_string(op.op_name, op.op_namelen, &str);
+ if (err)
+ return err;
+
+ err = copyin_string(op.op_buf, op.op_buflen, &tmp);
+ if (err) {
+ kfree(str);
+ return err;
+ }
+
+ 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)
+{
+ struct device_node *dp;
+ int err;
+
+ err = misc_register(&openprom_dev);
+ if (err)
+ return err;
+
+ dp = of_find_node_by_path("/");
+ dp = dp->child;
+ while (dp) {
+ if (!strcmp(dp->name, "options"))
+ break;
+ dp = dp->sibling;
+ }
+ options_node = dp;
+
+ 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/uctrl.c b/drivers/sbus/char/uctrl.c
new file mode 100644
index 000000000..57696fc0b
--- /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 "%s: uctrl regs[0x%p] (irq %d)\n",
+ op->dev.of_node->full_name, 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");