===== arch/sparc64/Kconfig 1.61 vs edited =====
--- 1.61/arch/sparc64/Kconfig	2005-01-10 13:11:03 -08:00
+++ edited/arch/sparc64/Kconfig	2005-02-02 18:20:46 -08:00
@@ -418,7 +418,7 @@
 	  macro in lp.c and the PARPORT_MAX macro in parport.h.
 
 config ENVCTRL
-	tristate "SUNW, envctrl support"
+	tristate "SUNW,envctrl support"
 	depends on PCI
 	help
 	  Kernel support for temperature and fan monitoring on Sun SME
@@ -426,6 +426,13 @@
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called envctrl.
+
+config ENVCTRLTWO
+	tristate "SUNW,envctrltwo environmental monitoring support"
+	depends on I2C_ENVCTRL
+	help
+	  This driver provides environmental monitoring and front status
+	  panel support for systems with SUNW,envctrltwo (Ultra Enterprise 250)
 
 config DISPLAY7SEG
 	tristate "7-Segment Display support"
===== arch/sparc64/defconfig 1.155 vs edited =====
--- 1.155/arch/sparc64/defconfig	2005-01-21 20:03:35 -08:00
+++ edited/arch/sparc64/defconfig	2005-02-02 18:21:04 -08:00
@@ -115,6 +115,7 @@
 CONFIG_PARPORT_1284=y
 CONFIG_PRINTER=m
 CONFIG_ENVCTRL=m
+CONFIG_ENVCTRLTWO=m
 CONFIG_DISPLAY7SEG=m
 # CONFIG_CMDLINE_BOOL is not set
 
===== arch/sparc64/kernel/Makefile 1.30 vs edited =====
--- 1.30/arch/sparc64/kernel/Makefile	2004-09-01 17:52:53 -07:00
+++ edited/arch/sparc64/kernel/Makefile	2005-02-15 08:45:23 -08:00
@@ -23,6 +23,9 @@
 obj-$(CONFIG_US3_FREQ) += us3_cpufreq.o
 obj-$(CONFIG_US2E_FREQ) += us2e_cpufreq.o
 obj-$(CONFIG_KPROBES) += kprobes.o
+obj-$(CONFIG_ENVCTRLTWO) += envctrltwo.o
+
+envctrltwo-objs := env_lib.o env_envctrltwo.o
 
 ifdef CONFIG_SUNOS_EMUL
   obj-y += sys_sunos32.o sunos_ioctl32.o
===== arch/sparc64/kernel/env_envctrltwo.c 1.1 vs edited =====
--- 1.1/arch/sparc64/kernel/env_envctrltwo.c	2005-02-03 10:12:38 -08:00
+++ edited/arch/sparc64/kernel/env_envctrltwo.c	2005-02-18 19:15:38 -08:00
@@ -0,0 +1,1012 @@
+/* $Id$
+ * env_envctrltwo.c: Environmental monitoring and front status panel
+ *                   support for SUNW,envctrltwo (E250)
+ *
+ * Copyright (C) 2004-2005 Eric Brower (ebrower@gmail.com)
+ * 
+ * TODOs: move overtemp handling to library code
+ *        implement diagnostic return values for all I2C-access functions
+ *        (i2c) i2c-core cannot be used in_interrupt() due to semaphores
+ * 	  (i2c) bus-atomicity for sending multiple messages (read,xor,write)
+ *	  better IRQ cause reporting/handling (see IDEAs below)
+ *
+ * IDEAs: monitor keyswich-- secure == sysctl kernel.stop-a = 0 
+ *                           diag   == sysctl kernel.printk increased loglevel
+ *	  override sysctl kernel.scons-poweroff if overtemp_action == poweroff
+ *	  decrease on-demand bus traffic with sensor periodic polling
+ *        periodic polling of ps and disk sensors (and interrupts) to set leds
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/miscdevice.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/kmod.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/reboot.h>
+#include <linux/sysfs.h>
+
+#include <asm/ebus.h>
+#include <asm/uaccess.h>
+#include <asm/auxio.h>
+
+#include <asm/i2c-envctrl.h>
+#include "env_envctrltwo.h"
+#include "env_lib.h"
+
+#define ENV2_DEVNAME	"envctrltwo"
+#define ENV2_VERSION    "0.2.0"
+
+#define ENV2_DEBUG_THERMS (0) /* dump thermisters found and values probed  */
+#define ENV2_DEBUG_FAN    (0) /* user fanspeed control via sysfs - DANGER! */
+#define ENV2_DEBUG_TEMP   (0) /* user CPU temp control via sysfs           */
+
+static struct pcf_envctrl_nexus *nex;
+
+/* these are referenced by env_lib-- perhaps an init() func would be better */
+struct i2c_adapter *envctrl_adap;
+spinlock_t envctrl_lock = SPIN_LOCK_UNLOCKED;
+
+/*
+ * OBP "thermisters" property
+ * Note: "name" is actually variable length,
+ * but null-padded to at least 4 characters
+ */
+struct env2_thermister_prop {
+	u32	addr;
+	u32	port;
+	u32	min_temp;
+	u32	warn_temp;
+	u32	shutdown_temp;
+	u32	num;
+	u32	den;
+	u8	name[4];
+}__attribute((packed));
+
+typedef enum env2_therm_type {
+	therm_type_cpu = 0,
+	therm_type_noncpu,
+} thermtype_t;
+
+struct therm {
+	struct env2_thermister_prop  *cfg;
+	thermtype_t                  type; 
+	unsigned long                last_warn;
+	struct list_head             list;
+};
+
+static struct list_head env2_therms = LIST_HEAD_INIT(env2_therms);
+static u8 env2_cpu_temp_table[256];
+static u8 env2_cpu_fanspeed_table[256];
+static u8 *env2_therms_data;
+
+static struct timer_list env2_fsp_act_timer;
+static unsigned int env2_fsp_irq;
+
+static struct task_struct *kenvctrld_task;
+
+static void env2_do_poweroff(void);
+static void env2_do_shutdown(void);
+static void (*env2_poweroff_pfn)(void) = NULL /* env2_do_poweroff */;
+static int env2_fanspeed_forced = 0;
+
+/* sysfs sh!te goes here... */
+static struct platform_device *env2_device;
+
+static ssize_t env2_show_fanspeed(struct device *d, char *buf);
+#if ENV2_DEBUG_FAN
+	static ssize_t env2_store_fanspeed(
+		struct device *, const char *, size_t);
+	static DEVICE_ATTR(fanspeed, \
+		S_IRUGO|S_IWUSR, env2_show_fanspeed, env2_store_fanspeed);
+#else 
+	static DEVICE_ATTR(fanspeed, S_IRUGO, env2_show_fanspeed, NULL);
+#endif
+#if ENV2_DEBUG_TEMP
+	static u8 env2_forced_temp;
+	static ssize_t env2_store_temp(struct device *, const char *, size_t);
+	static ssize_t env2_show_temp (struct device *, char *buf);
+	static DEVICE_ATTR(forced_temp, \
+		S_IRUGO | S_IWUSR, env2_show_temp, env2_store_temp);
+#endif
+static ssize_t env2_show_thermisters(struct device *d, char *buf);
+static DEVICE_ATTR(thermisters, S_IRUGO, env2_show_thermisters, NULL);
+/* TODO: store_power for PS status override ??? */
+static ssize_t env2_show_power(struct device *d, char *buf);
+static DEVICE_ATTR(power, S_IRUGO, env2_show_power, NULL);
+/* TODO: store_disks for disk status override ??? */
+static ssize_t env2_show_disks(struct device *d, char *buf);
+static ssize_t env2_store_disks(struct device *d, const char *buf, size_t cnt);
+static DEVICE_ATTR(disks, S_IRUGO|S_IWUSR, env2_show_disks, env2_store_disks);
+static ssize_t env2_show_keyswitch(struct device *d, char *buf);
+static DEVICE_ATTR(keyswitch, S_IRUGO, env2_show_keyswitch, NULL);
+/* we can set LEDs, but until we have bus-atomicity it is racy */
+static ssize_t env2_show_fspleds(struct device *d, char *buf);
+static ssize_t env2_store_fspleds(struct device *d, const char *b, size_t c);
+static DEVICE_ATTR(fspleds, \
+	S_IRUGO|S_IWUSR, env2_show_fspleds, env2_store_fspleds);
+
+/* specify "envctrl_overtemp=" to override the default over-temperature
+ * action.  Allowable values are poweroff, shutdown and none (dangerous!).
+ * The default is shutdown to be idiomatic with existing Linux/SPARC envctrl
+ * drivers.
+ * They all ought to be "poweroff" for safety-- though this requires
+ * tweaking the scons_poweroff sysctl on kernels that support it,
+ * otherwise serial console based systems will not poweroff properly.
+ */
+static int __init env2_parse_overtemp_option(const char *str)
+{
+	if (NULL == str) {
+		return 1;
+	}
+ 
+	if (!strncmp(str, "poweroff", strlen("poweroff"))) {
+		env2_poweroff_pfn = env2_do_poweroff;
+		return 1;
+	}
+	else if (!strncmp(str, "shutdown", strlen("shutdown"))) {
+		env2_poweroff_pfn = env2_do_shutdown;
+		return 1;
+	}
+#if ENV2_DEBUG_TEMP
+	else if (!strncmp(str, "none", strlen("none"))) {
+		env2_poweroff_pfn = NULL;
+		printk(KERN_INFO 
+			"%s: WARNING: thermal shutdown/power-off disabled!\n",
+			ENV2_DEVNAME);
+		return 1;
+	}
+#endif
+	else {
+		printk(KERN_ERR 
+			"%s: unrecognized envctrl_overtemp value \"%s\" ignored\n", 
+			ENV2_DEVNAME, str);
+	}
+	return 0;
+}
+
+static const char *envctrl_overtemp;
+MODULE_PARM (envctrl_overtemp, "s");
+MODULE_PARM_DESC(envctrl_overtemp,
+        "over-temperature action: poweroff or shutdown (default)");
+
+static int __init env2_init_thermisters(struct linux_ebus_device *edev) 
+{
+	int i;
+	int ntherms = 0;
+	struct env2_thermister_prop *t_itr;
+	u8 *start, *end;
+
+	struct therm *therm;
+	struct list_head *tmp1, *tmp2;	
+
+	/*
+	 * The lookup table length is short by two entries that 
+	 * represent the coolest thermister raw values 
+	 * (indecies 254, 255).  These lower values are reasonably
+	 * expected, so we allocate a bigger table and shift the table
+	 * values down by two.  We'll duplicate the value at the top of
+	 * the table for the two new slots (though these are cooking
+	 * temperatures we'd not expect to see).
+	 */
+	i = prom_getproplen(edev->prom_node, "cpu-temp-factors");
+	if (i != 254) {
+		printk(KERN_ERR "%s: unexpected cpu-temp-factors table size " \
+			"(is %u, expected %u)\n", ENV2_DEVNAME, i, 254);
+		goto out_err;
+	}
+
+	i = prom_getproperty(edev->prom_node, "cpu-temp-factors",
+		(u8*)(&env2_cpu_temp_table) + 2, i);
+	if (i == -1) {
+		printk(KERN_ERR "%s: unable to read OBP cpu temp table\n",
+			ENV2_DEVNAME);
+		goto out_err;
+	}
+	env2_cpu_temp_table[0] = \
+		env2_cpu_temp_table[1] = env2_cpu_temp_table[2];
+
+	/* Same drill with cpu-fan-speeds table...
+	 *
+	 * Observed behavior differs from OBP cpu-fan-speeds table;
+	 * it seems that 162 is the minimum speed at which the fan can run,
+	 * so don't be confused by the discrepancy between table and readings
+	 * at the low end of the scale.
+	 */
+	i = prom_getproplen(edev->prom_node, "cpu-fan-speeds");
+	if (i != 254) {
+		printk(KERN_ERR "%s: unexpected cpu-fan-speeds table size " \
+			"(is %u expected %u)\n", ENV2_DEVNAME, i, 254);
+		goto out_err;
+	}
+	
+	i = prom_getproperty(edev->prom_node, "cpu-fan-speeds",
+		(u8*)(&env2_cpu_fanspeed_table) + 2, i);
+	if (i == -1) {
+		printk(KERN_ERR "%s: unable to read OBP cpu-fan-speeds table\n",
+			ENV2_DEVNAME);
+		goto out_err;
+	}
+	env2_cpu_fanspeed_table[0] = \
+		env2_cpu_fanspeed_table[1] = env2_cpu_fanspeed_table[2];
+
+	i = prom_getproplen(edev->prom_node, "thermisters");
+	if (!i) {
+		printk(KERN_ERR "%s: no thermister data found in OBP\n",
+			ENV2_DEVNAME);
+		goto out_err;
+	}
+	env2_therms_data = (u8*) kmalloc(i, GFP_KERNEL);
+	if (!env2_therms_data) {
+		printk(KERN_ERR "%s: failed to malloc thermister table\n",
+			ENV2_DEVNAME);
+		goto out_err;
+	}
+	if (! prom_getproperty(edev->prom_node, "thermisters",
+			       env2_therms_data, i)) {
+		printk(KERN_ERR "%s: unable to collect thermister data\n",
+			ENV2_DEVNAME);
+		goto out_err;
+	}
+	
+	start = env2_therms_data;
+	end   = start + i - 1;
+
+	while (start < end &&
+	      ((end - start) >= sizeof(struct env2_thermister_prop))) {
+		t_itr = (struct env2_thermister_prop *) start;
+		start = t_itr->name + strlen(t_itr->name) + 1;
+
+		therm = kmalloc(sizeof(struct therm), GFP_KERNEL);
+		if (!therm) {
+			goto out_err;
+		}
+
+		/* init one thermister */
+		therm->cfg = t_itr;
+		therm->last_warn = 0;
+		therm->type = 
+			(!strncmp(therm->cfg->name, "CPU", strlen("CPU"))) ?
+			therm_type_cpu : therm_type_noncpu;
+		list_add_tail(&(therm->list), &env2_therms);
+		ntherms++;
+	}
+	return ntherms;
+
+out_err:
+	if (env2_therms_data) {
+		kfree(env2_therms_data);
+	}
+	list_for_each_safe(tmp1, tmp2, &env2_therms) {
+		therm = list_entry(tmp1, struct therm, list);
+		list_del(tmp1);
+		kfree(therm);
+	}
+	return 0;
+}
+
+static irqreturn_t env2_fsp_intr(int irq, void *dev_id, struct pt_regs *regs)
+{
+	/* TODO:
+	 * known interrupt sources by inverted bit:
+	 *   0xBF - PS 
+	 *   0xDF - Keyswitch
+	 *   0xEF - Fan
+	 *   0xFB - Disk (0 & 1 tested) 
+	 */
+	u8 srcs = envctrl_i2c_read_8574(ENV2_FSP_CTRL_ADDR);
+	envctrl_i2c_write_8574(ENV2_FSP_CTRL_ADDR, 0xFF); /* Ack All... */
+        printk(KERN_DEBUG "envctrl: received FSP interrupt (ctrl 0x%02x)\n",
+		srcs);
+
+	return IRQ_HANDLED;
+}
+
+static void env2_fsp_activity(unsigned long unused)
+{
+	u8 rd;
+	unsigned long flags;
+
+	/* TODO: this must be made i2c-atomic-- not just spinlocked */
+	spin_lock_irqsave(&envctrl_lock, flags);
+
+	__envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR);
+	/* always write keyswitch mask-- key bits can be unset, but not set! */
+	rd = (__envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR) ^ ENV2_LED_ACTIVE) 
+		| ENV2_FSP_KEYMSK;
+	__envctrl_i2c_write_8574(ENV2_FSP_LED_ADDR, rd);
+
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+
+	mod_timer(&env2_fsp_act_timer, jiffies + (HZ/2));
+}
+
+static int __init env2_init_fsp(struct linux_ebus_device *edev) 
+{
+
+	u8 leds;
+
+	if (edev->num_irqs != 2) {
+		printk(KERN_ERR "%s: unexpected number of interrupts "
+			"(is %i expected %i)\n", 
+			ENV2_DEVNAME, edev->num_irqs, 2);
+		return 0;
+	}
+
+	env2_fsp_irq = edev->irqs[1];
+	if (request_irq(env2_fsp_irq, &env2_fsp_intr,
+			SA_SHIRQ, "SUNW,envctrltwo FSP",
+			(void *)&env2_fsp_act_timer)) {
+		printk(KERN_ERR "%s: unable to register FSP irq\n", 
+			ENV2_DEVNAME);
+		env2_fsp_irq = 0;
+		return 0;
+	}
+
+	/* 
+	 * FSP GEN_ERR LED is plumbed OR'ed with auxio LED bit.
+	 * Extinguish it, otherwise system appears in error
+	 */
+	auxio_set_led(AUXIO_LED_OFF);
+
+	leds = envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR);
+	envctrl_i2c_write_8574(ENV2_FSP_LED_ADDR, leds | ENV2_FSP_LEDMSK);
+
+	init_timer(&env2_fsp_act_timer);
+	env2_fsp_act_timer.expires  = jiffies + (HZ/2);
+	env2_fsp_act_timer.function = env2_fsp_activity;
+	add_timer(&env2_fsp_act_timer);
+	 
+	/* Enable FSP interrupts */
+	envctrl_i2c_write_8574(ENV2_FSP_CTRL_ADDR, ENV2_FSP_DFLOP_INIT0);
+	envctrl_i2c_write_8574(ENV2_FSP_CTRL_ADDR, ENV2_FSP_DFLOP_INIT1);
+	envctrl_i2c_write_8574(ENV2_FSP_CTRL_ADDR, ENV2_FSP_DEVINTR_INIT0);
+	envctrl_i2c_write_8574(ENV2_FSP_CTRL_ADDR, ENV2_FSP_DEVINTR_INIT1);
+
+	return 1;
+}
+
+/* env2_read_thermister: reads a thermister and returns a translated value
+ *   if "raw" is non-null, the raw thermister reading will be stored into it
+ */
+static u8 env2_read_thermister(struct therm *t, u8 *raw)
+{
+	u8 val;
+	int ret;
+
+	switch (t->cfg->addr & 0xF0) {
+		case 0x90:
+			ret = envctrl_i2c_read_8591(
+				t->cfg->addr, t->cfg->port, &val);
+			break;
+		default:
+			/* BUG() */
+			return 0;
+	}
+
+	if (ret) {
+		printk(KERN_ERR "%s: unable to read thermister %s%s\n",
+			ENV2_DEVNAME, t->cfg->name,
+			(ret == -EINTR)?" (lost arbitration-- transient)":"");
+		return 0;
+	}
+	if (raw) {
+		*raw = val;
+	}
+#if ENV2_DEBUG_THERMS
+	printk("%s: %s raw: %u ", __FUNCTION__, t->cfg->name, val);
+#endif
+	if (t->type == therm_type_cpu) {
+		val = env2_cpu_temp_table[val];
+	}
+	else if (t->cfg->num && t->cfg->den) {
+		val = (val * t->cfg->num) / t->cfg->den;
+	}
+	else {
+		/* perhaps raw? */
+		printk(KERN_WARNING
+			"%s: no thermister translation for %s (val %i)\n",
+			ENV2_DEVNAME, t->cfg->name, val);
+	}
+#if ENV2_DEBUG_THERMS
+	printk("cooked: %u\n", val);
+#endif
+	return val;
+}
+
+/* TODO: change to include return value */
+static void env2_set_fanspeed(u8 fanspeed, u8 forced)
+{
+	if (!forced && env2_fanspeed_forced) {
+		return;
+	}
+	envctrl_i2c_write_8591(ENV2_FAN_ADDR, fanspeed);
+}
+
+/* TODO: change for meaningful return value */
+static u8 env2_get_fanspeed(void)
+{
+	u8 rv;
+	int val;
+	/* TODO: raw values read from the fan do not correspond to
+ 	* the values written (even after suitable settling time).
+ 	* This is likely due to approximations in the D2A, so we might
+ 	* as well artificially inflate the read values here...
+	* 
+	* Ugh-- consider wraparound at high speeds...
+ 	*/
+	if (! envctrl_i2c_read_8591(ENV2_FAN_ADDR, ENV2_FAN_PORT, &rv)) {
+		val = rv * 107 / 100;
+		if (val > 255 /* UCHAR_MAX */) {
+			return 255; /* UCHAR_MAX or FAN_MAX */
+		}
+		return (u8) val;
+	}
+	return 0;
+}
+
+static ssize_t env2_show_fanspeed(struct device *dev, char *buf)
+{
+	return sprintf(buf, "FAN: [%u]%s\n", 
+		env2_get_fanspeed(), env2_fanspeed_forced ? " (forced)" : "");
+}
+
+#if ENV2_DEBUG_FAN
+static ssize_t env2_store_fanspeed(struct device *dev, const char *buf, size_t count)
+{
+	u8 val = (u8) simple_strtoul(buf, NULL, 0);
+	if (!val) {
+		/* writing 0 un-forces fan speed */
+		env2_fanspeed_forced = 0;
+		return count;
+	}
+
+	env2_fanspeed_forced = 1;
+	env2_set_fanspeed(val, 1);
+
+	return count;
+}
+#endif /* ENV2_DEBUG_FAN */
+
+#if ENV2_DEBUG_TEMP
+static ssize_t env2_store_temp(struct device *d, const char *buf, size_t count)
+{
+	u8 val = (u8) simple_strtoul(buf, NULL, 0);
+	env2_forced_temp = val;
+	return count;
+}
+
+static ssize_t env2_show_temp (struct device *d, char *buf)
+{
+	return sprintf(buf, "%i\n", env2_forced_temp);
+}
+#endif
+
+static ssize_t env2_show_thermisters(struct device *dev, char *buf)
+{
+	ssize_t ret = 0;
+	struct therm *t;
+	struct list_head *tmp1, *tmp2;	
+
+	list_for_each_safe(tmp1, tmp2, &env2_therms) {
+		t = list_entry(tmp1, struct therm, list);
+#if ENV2_DEBUG_THERMS
+		ret += sprintf(buf + ret, 
+			"%s:\t%u C (0x%02x[%u] min %i, warn %i, shut %i, "
+			"num %i, den %i, type %i)\n",
+			t->cfg->name, env2_read_thermister(t, NULL),
+			t->cfg->addr, t->cfg->port, t->cfg->min_temp, 
+			t->cfg->warn_temp, t->cfg->shutdown_temp, 
+			t->cfg->num, t->cfg->den, t->type);
+#else
+		ret += sprintf(buf + ret, 
+			"%s:\t[%u C] (min %i, warn %i, shut %i)\n",
+			t->cfg->name, env2_read_thermister(t, NULL),
+			t->cfg->min_temp, t->cfg->warn_temp, 
+			t->cfg->shutdown_temp);
+#endif
+	}
+	return ret;
+}
+
+static u8 env2_get_power(void)
+{
+	return envctrl_i2c_read_8574(ENV2_PS_ADDR);
+}
+
+static ssize_t env2_show_power(struct device *dev, char *buf)
+{
+	ssize_t ret = 0;
+	u8 stat = env2_get_power();
+
+	ret += sprintf(buf+ret, "PS1: [%s]\n",
+		(ENV2_PS_PRES(stat,1) ? 
+			(ENV2_PS_ERR(stat,1) ? "FAIL" : "OK") : "ABSENT"));
+	ret += sprintf(buf+ret, "PS2: [%s]\n",
+		(ENV2_PS_PRES(stat,2) ? 
+			(ENV2_PS_ERR(stat,2) ? "FAIL" : "OK") : "ABSENT"));
+	return ret;	
+}
+
+static u8 env2_get_diskpres(void)
+{
+	return envctrl_i2c_read_8574(ENV2_DISKPRES_ADDR);
+}
+
+static u8 env2_get_diskstat(void)
+{
+	return envctrl_i2c_read_8574(ENV2_DISKSTAT_ADDR);
+}
+
+static ssize_t env2_show_disks(struct device *dev, char *buf)
+{
+	ssize_t ret = 0;
+	u8 pres = env2_get_diskpres();
+	u8 stat = env2_get_diskstat();
+	int i; 
+	for (i = 1; i <= ENV2_DISK_MAX; i++) {
+		ret += sprintf(buf+ret, "DISK%i: [%s]\n", i,
+			(!(pres & (1<<(i-1))) ? 
+				((!(stat & (1<<(i-1)))) ? "FAIL" : "OK") : 
+				((!(stat & (1<<(i-1)))) ? "ABSENT/ATTN"  : 
+				"ABSENT")));
+	}
+
+	return ret;	
+}
+
+static ssize_t env2_store_disks(struct device *dev, const char *buf, size_t count)
+{
+	u8 stat;
+
+	/* We don't first check the disk is present-- we could, though */
+	u8 diskno = (u8) simple_strtoul(buf, NULL, 0);
+	if (diskno && diskno <= ENV2_DISK_MAX) {
+		char *val = strchr(buf, '=');
+		if (val) {
+			val++;
+			if (!strnicmp(val, "fail", strlen("fail"))) {
+				/* TODO: this must be made bus-atomic */
+				spin_lock(&envctrl_lock);
+				stat = 
+				  __envctrl_i2c_read_8574(ENV2_DISKSTAT_ADDR);
+				stat &= ~(1 << (diskno - 1));
+				__envctrl_i2c_write_8574(
+					ENV2_DISKSTAT_ADDR, stat);
+				spin_unlock(&envctrl_lock);
+			}
+			else if (!strnicmp(val, "ok", strlen("ok"))) {
+				/* TODO: this must be made bus-atomic */
+				spin_lock(&envctrl_lock);
+				stat =
+				  __envctrl_i2c_read_8574(ENV2_DISKSTAT_ADDR);
+				stat |= (1 << (diskno - 1));
+				__envctrl_i2c_write_8574(
+					ENV2_DISKSTAT_ADDR, stat);
+				spin_unlock(&envctrl_lock);
+			}
+		}
+	}
+	return count;
+}
+
+static u8 env2_get_keyswitch(void)
+{
+	return envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR) & ENV2_FSP_KEYMSK;
+}
+
+static ssize_t env2_show_keyswitch(struct device *dev, char *buf)
+{
+	ssize_t ret = 0;
+	u8 stat = env2_get_keyswitch();
+	char *output;
+
+	switch (stat) {
+		case ENV2_FSP_KEY_OFF: 
+			output = "OFF"; 
+			break;
+		case ENV2_FSP_KEY_DIAG:
+			output = "DIAG";
+			break;
+		case ENV2_FSP_KEY_ON:
+			output = "ON";
+			break;
+		case ENV2_FSP_KEY_LOCK:
+			output = "LOCKED";
+			break;
+		default:
+			output = "UNKNOWN";
+			break;
+	}
+
+	ret += sprintf(buf + ret, "KEYSWITCH: [%s]\n", output);
+
+	return ret;	
+}
+
+static u8 env2_get_fspleds(void)
+{
+	return envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR) & ENV2_FSP_LEDMSK;
+}
+
+/* TODO: diagnostic return value */
+static void env2_set_fspleds(u8 leds_on, u8 leds_off)
+{
+	u8 rd;
+	unsigned long flags;
+
+	/* don't stomp on keyswitch bits */
+	if ((leds_on | leds_off) & ENV2_FSP_KEYMSK) {
+		return;
+	}
+
+	/* TODO: this must be made i2c-atomic-- not just spinlocked */
+	spin_lock_irqsave(&envctrl_lock, flags);
+
+	__envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR);
+	/* always write keyswitch mask-- key bits can be unset, but not set! */
+	rd = (__envctrl_i2c_read_8574(ENV2_FSP_LED_ADDR) | 
+		ENV2_FSP_KEYMSK | leds_off) & ~leds_on;
+	__envctrl_i2c_write_8574(ENV2_FSP_LED_ADDR, rd);
+
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+}
+
+static ssize_t env2_show_fspleds(struct device *dev, char *buf)
+{
+	ssize_t ret = 0;
+	u8 stat = env2_get_fspleds();
+
+	ret += sprintf(buf + ret, "POWER:      [%s]\n", "ON"); 
+	ret += sprintf(buf + ret, "GEN ERROR:  [%s]\n", 
+		stat & ENV2_LED_GENERR ? "OFF" : "ON");
+	ret += sprintf(buf + ret, "ACTIVITY:   [%s]\n", 
+		stat & ENV2_LED_ACTIVE ? "OFF" : "ON");
+	ret += sprintf(buf + ret, "DISK ERROR: [%s]\n", 
+		stat & ENV2_LED_DISKERR ? "OFF" : "ON");
+	ret += sprintf(buf + ret, "TEMP ERROR: [%s]\n", 
+		stat & ENV2_LED_DISKERR ? "OFF" : "ON");
+	ret += sprintf(buf + ret, "PS ERROR:   [%s]\n", 
+		stat & ENV2_LED_PSERR ? "OFF" : "ON");
+
+	return ret;	
+}
+
+static ssize_t env2_store_fspleds(struct device *dev, const char *buf, size_t cnt)
+{
+	char on = -1;
+	char led= -1;
+
+	char *val = strchr(buf, '=');
+	if (!val) {
+		return cnt;
+	}
+	val++;
+	
+	if (!strnicmp(val, "on", strlen("on"))) {
+		on = 1;
+	}
+	else if (!strnicmp(val, "off", strlen("off"))) {
+		on = 0;
+	}
+	else {
+		return cnt;
+	}
+
+	if (!strnicmp(buf, "temp=", strlen("temp="))) {
+		led = ENV2_LED_TEMPERR;
+	}
+	else if (!strnicmp(buf, "disk=", strlen("disk="))) {
+		led = ENV2_LED_DISKERR;
+	}
+	else if (!strnicmp(buf, "ps=", strlen("ps="))) {
+		led = ENV2_LED_PSERR;
+	}
+	else if (!strnicmp(buf, "all=", strlen("all="))) {
+		led = ENV2_LED_PSERR | ENV2_LED_DISKERR | ENV2_LED_TEMPERR;
+	}
+	else {
+		printk("%s: unknown led specified\n", __FUNCTION__);
+		return cnt;
+	}
+
+	env2_set_fspleds((on ? led : 0), on ? 0 : led);
+
+	return cnt;
+}
+
+
+static void env2_do_poweroff(void)
+{
+	static int po_inprog = 0;
+
+	if (po_inprog) {
+		return;
+	}
+
+	po_inprog = 1;
+	printk(KERN_CRIT "%s: WARNING: Powering-off the system now!\n",
+		current->comm);
+
+	/* TODO: machine_power_off will go to OBP instead if this system
+	 * is serial-console based and scons_poweroff sysctl is not set
+	 */
+	machine_power_off();
+}
+
+static void env2_do_shutdown(void)
+{
+	static int sd_inprog = 0;
+	static char *envp[] = {
+		"HOME=/", "TERM=linux", 
+		"PATH=/sbin:/usr/sbin:/bin:/usr/bin", NULL };
+	char *argv[] = { "/sbin/shutdown", "-h", "now", NULL };
+ 
+	if (sd_inprog != 0)
+		return;
+ 
+	sd_inprog = 1;
+
+	printk(KERN_CRIT "%s: WARNING: Shutting down the system now.\n",
+		current->comm);
+#if 0
+	if (0 > execve("/sbin/shutdown", argv, envp)) {
+#else
+	if (call_usermodehelper("/sbin/shutdown", argv, envp, 0)) {
+#endif
+		printk(KERN_CRIT "%s: WARNING: system shutdown failed!\n",
+			current->comm);
+		/* unlikely to succeed, but we could try again */
+		sd_inprog = 0;  
+	}
+}
+
+#define WARN_INTERVAL (30 * HZ)
+static void env2_check_therms(void)
+{
+	u8 tempbuf;
+	u8 tempmax = 0;
+	u8 rawbuf;
+	u8 tempmax_raw  = 0;
+
+	struct list_head *tmp;
+	struct therm     *t;
+
+	list_for_each(tmp, &env2_therms) {
+		t = list_entry(tmp, struct therm, list);
+		tempbuf = env2_read_thermister(t, &rawbuf);
+		if (t->type == therm_type_cpu) {
+#if ENV2_DEBUG_TEMP
+			if (env2_forced_temp)  { 
+				tempbuf = env2_forced_temp; 
+			}
+#endif
+			if (tempbuf > tempmax) {
+				tempmax      = tempbuf;
+				tempmax_raw  = rawbuf;
+			}
+		}
+		if (t->cfg->shutdown_temp && tempbuf >= t->cfg->shutdown_temp) {
+			printk(KERN_CRIT
+				"%s: WARNING: %s temperature %u C meets or "
+				"exceeds shutdown threshold %u C\n",
+				ENV2_DEVNAME, t->cfg->name, 
+				tempbuf, t->cfg->shutdown_temp); 
+
+			env2_set_fspleds(ENV2_LED_GENERR, 0);
+			env2_set_fanspeed(ENV2_FAN_MAX, 0);
+
+			if (env2_poweroff_pfn) {
+				env2_poweroff_pfn();
+			}
+			return;
+		}
+		else if (t->cfg->warn_temp && tempbuf >= t->cfg->warn_temp) {
+			/* TODO: set thermal warning LED or gen_err */
+			if (time_after(jiffies, t->last_warn + WARN_INTERVAL)) {
+				t->last_warn = jiffies;
+				printk(KERN_CRIT
+					"%s: WARNING: %s temperature %u C "
+					"meets or exceeds warning threshold "
+					"%u C\n",
+					ENV2_DEVNAME, t->cfg->name,
+					tempbuf, t->cfg->warn_temp);
+				printk(KERN_CRIT
+					"%s: WARNING: system will shut down "
+					"at %s temperature %u C\n",
+					ENV2_DEVNAME, t->cfg->name,
+					t->cfg->shutdown_temp);
+			}
+		}
+		else {
+			/* Use last_warn as a flag to reduce I2C traffic--
+			 * because there is no hysteresis, this could cause 
+			 * unlimited warn messages if we hover around warn_temp
+			 */
+			if (t->last_warn) {
+				t->last_warn = 0;
+				// TODO: env2_set_led functionality
+				//env2_set_led(ENVX_LED_TYPE_GEN_ERR, ENVX_LED_OFF);
+				env2_set_fspleds(0, ENV2_LED_GENERR);
+			}
+		}
+	}
+	env2_set_fanspeed(env2_cpu_fanspeed_table[tempmax_raw], 0);
+}
+
+static int kenvctrld(void *__unused)
+{
+	int poll_interval = 5 * HZ;
+
+	daemonize("kenvctrld");
+	allow_signal(SIGKILL);
+
+	printk(KERN_INFO "%s: %s starting\n", ENV2_DEVNAME, current->comm);
+	while (!kthread_should_stop()) {
+		current->state = TASK_INTERRUPTIBLE;
+		schedule_timeout(poll_interval);
+		current->state = TASK_RUNNING;
+
+		if (signal_pending(current)) {
+			break;
+		}
+		
+		env2_check_therms();
+	}
+	printk(KERN_INFO "%s: %s exiting\n", ENV2_DEVNAME, current->comm);
+
+	return 0;
+}
+
+static void __exit env2_cleanup(void)
+{
+	/* XXX: env2_cleanup_thermisters(), env2_cleanup_fsp(), etc */
+	struct therm *therm;
+	struct list_head *tmp1, *tmp2;	
+
+	if (env2_device) {
+		device_remove_file(&(env2_device->dev), &dev_attr_fanspeed);
+		device_remove_file(&(env2_device->dev), &dev_attr_thermisters);
+		device_remove_file(&(env2_device->dev), &dev_attr_power);
+		device_remove_file(&(env2_device->dev), &dev_attr_disks);
+		device_remove_file(&(env2_device->dev), &dev_attr_keyswitch);
+		device_remove_file(&(env2_device->dev), &dev_attr_fspleds);
+#if ENV2_DEBUG_TEMP
+		device_remove_file(&(env2_device->dev), &dev_attr_forced_temp);
+#endif
+		platform_device_unregister(env2_device);
+		env2_device = NULL;
+	}
+
+	if (kenvctrld_task) {
+		(void) kthread_stop(kenvctrld_task);
+		kenvctrld_task = NULL;
+	}
+
+	if (env2_fsp_irq) {
+		/* disable FSP interrupts */
+		envctrl_i2c_write_8574(
+			ENV2_FSP_CTRL_ADDR, ENV2_FSP_INTR_LATCH_CLR);
+		free_irq(env2_fsp_irq, (void *) &env2_fsp_act_timer);
+		env2_fsp_irq = 0;
+	}
+	del_timer_sync(&env2_fsp_act_timer);
+
+	if (env2_therms_data) {
+		kfree(env2_therms_data);
+		env2_therms_data = NULL;
+	}
+	list_for_each_safe(tmp1, tmp2, &env2_therms) {
+		therm = list_entry(tmp1, struct therm, list);
+		list_del(tmp1);
+		kfree(therm);
+	}
+
+	if (envctrl_adap) {
+		i2c_put_adapter(envctrl_adap);
+		envctrl_adap = NULL;
+	}
+
+	return;
+}
+
+#if ENV2_DEBUG_THERMS
+static void dump_therms(void)
+{
+	struct therm *t;
+	struct list_head *tmp1, *tmp2;	
+
+	list_for_each_safe(tmp1, tmp2, &env2_therms) {
+		t = list_entry(tmp1, struct therm, list);
+		printk("0x%02x[%u] min %i, warn %i, shut %i, "
+			"num %i, den %i, %s\n",
+			t->cfg->addr, t->cfg->port, t->cfg->min_temp, 
+			t->cfg->warn_temp, t->cfg->shutdown_temp, 
+			t->cfg->num, t->cfg->den, t->cfg->name);
+	}
+}
+#endif
+
+static int __init env2_init(void)
+{
+	int err = 0;
+
+	env2_parse_overtemp_option(envctrl_overtemp);
+
+	nex = pcf_envctrl_get_nexus();
+	if (nex->type != TYPE_ENVCTRLTWO) {
+		printk("%s: nexus type %i is not ENVCTRLTWO\n", 
+			__FUNCTION__, nex->type);
+		return -ENODEV;
+	}
+
+	envctrl_adap = i2c_get_adapter(nex->i2c_id);
+	if (!envctrl_adap) {
+		printk("%s: i2c_get_adapter_id(%i) returned NULL\n",
+			__FUNCTION__, nex->i2c_id);
+		return -ENODEV;
+	}
+
+	if (! env2_init_thermisters(nex->edev)) {
+		err = -EINVAL;
+		goto out_err;
+	}
+
+#if ENV2_DEBUG_THERMS
+	dump_therms();
+#endif
+
+	if (! env2_init_fsp(nex->edev)) {
+		err = -EINVAL;
+		goto out_err;
+	}
+
+	kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld");
+	if (IS_ERR(kenvctrld_task)) {
+		err = PTR_ERR(kenvctrld_task);
+		kenvctrld_task = NULL;
+		goto out_err;
+	}
+
+	env2_device = platform_device_register_simple(
+		ENV2_DEVNAME "-", 0, NULL, 0);
+	if (IS_ERR(env2_device)) {
+		err = PTR_ERR(env2_device);
+		env2_device = NULL;
+		goto out_err;
+	}
+	device_create_file(&(env2_device->dev), &dev_attr_fanspeed);
+	device_create_file(&(env2_device->dev), &dev_attr_thermisters);
+	device_create_file(&(env2_device->dev), &dev_attr_power);
+	device_create_file(&(env2_device->dev), &dev_attr_disks);
+	device_create_file(&(env2_device->dev), &dev_attr_keyswitch);
+	device_create_file(&(env2_device->dev), &dev_attr_fspleds);
+#if ENV2_DEBUG_TEMP
+	device_create_file(&(env2_device->dev), &dev_attr_forced_temp);
+#endif
+
+	printk(KERN_INFO "%s: driver version %s initialized\n", 
+		ENV2_DEVNAME, ENV2_VERSION);
+
+	return 0;
+
+out_err:
+	env2_cleanup();
+	return err;
+}
+
+
+module_init(env2_init);
+module_exit(env2_cleanup);
+MODULE_LICENSE("GPL");
===== arch/sparc64/kernel/env_envctrltwo.h 1.1 vs edited =====
--- 1.1/arch/sparc64/kernel/env_envctrltwo.h	2005-02-03 10:12:42 -08:00
+++ edited/arch/sparc64/kernel/env_envctrltwo.h	2005-02-15 08:45:01 -08:00
@@ -0,0 +1,54 @@
+
+/* $Id$
+ * env_envctrltwo.h - SUNW,envctrltwo private definitions
+ * Copyright (C) 2004-2005 Eric Brower (ebrower@gmail.com)
+ */
+
+#ifndef _SPARC64_ENV_ENVCTRLTWO_H
+#define _SPARC64_ENV_ENVCTRLTWO_H
+
+/* 
+ * envctrltwo definitions 
+ */
+#define ENV2_FSP_LED_ADDR 	0x7C
+#define ENV2_LED_DISKERR 	(1<<0)
+#define ENV2_LED_PSERR   	(1<<1)
+#define ENV2_LED_TEMPERR 	(1<<2)
+#define ENV2_LED_GENERR  	(1<<3)
+#define ENV2_LED_ACTIVE  	(1<<4)
+#define ENV2_LED_POWER   	(1<<5)
+#define ENV2_FSP_LEDMSK 	0x3F
+
+#define ENV2_FSP_KEY_OFF	0x00
+#define ENV2_FSP_KEY_DIAG       0x40
+#define ENV2_FSP_KEY_ON         0x80
+#define ENV2_FSP_KEY_LOCK       0xC0
+#define ENV2_FSP_KEYMSK         0xC0
+
+#define ENV2_FSP_CTRL_ADDR      0x70
+#define ENV2_FSP_DFLOP_INIT0    0x77
+#define ENV2_FSP_DFLOP_INIT1    0x7F
+#define ENV2_FSP_DEVINTR_INIT0  0xF7
+#define ENV2_FSP_DEVINTR_INIT1  0xFF
+#define ENV2_FSP_INTR_LATCH_CLR 0xFE
+
+#define ENV2_FAN_ADDR 		0x94
+#define ENV2_FAN_PORT 		0x01
+#define ENV2_FAN_MAX  		0xFF
+
+#define ENV2_DISKPRES_ADDR 	0x7A
+#define ENV2_DISKSTAT_ADDR 	0x7E
+#define ENV2_DISK_MAX      	6	/* 1-based */
+
+#define ENV2_PS_ADDR 		0x72
+#define ENV2_PS_PRES_MASK	0x03
+#define ENV2_PS_PRES_SHIFT	0
+#define ENV2_PS_PRES(stat, ps)	\
+	(!(((stat & ENV2_PS_PRES_MASK) >> (ENV2_PS_PRES_SHIFT + ps - 1)) & 0x01))
+#define ENV2_PS_ERR_MASK	0x30
+#define ENV2_PS_ERR(stat, ps)	\
+	(!(((stat & ENV2_PS_ERR_MASK) >> (ENV2_PS_ERR_SHIFT + ps - 1)) & 0x01))
+#define ENV2_PS_ERR_SHIFT	4
+#define ENV2_PS_MAX		2	/* 1-based */
+
+#endif /* #ifndef _SPARC64_ENV_ENVCTRLTWO_H */
===== arch/sparc64/kernel/env_lib.c 1.1 vs edited =====
--- 1.1/arch/sparc64/kernel/env_lib.c	2005-02-14 23:55:51 -08:00
+++ edited/arch/sparc64/kernel/env_lib.c	2005-02-15 08:45:01 -08:00
@@ -0,0 +1,181 @@
+/* $Id$
+ * env_lib.c: Environmental monitoring library code for 
+ *            accessing I2C clients (chips) via i2c-envctrl 
+ *            PCF8584 bus nexus interface 
+ *
+ * Copyright (C) 2004-2005 Eric Brower (ebrower@gmail.com)
+ * 
+ * TODO: functions should all return explicit error codes
+ */
+
+#include <linux/spinlock.h>
+#include "env_lib.h"
+
+/* TODO-- get references from an env_lib_init() routine
+ */
+extern struct i2c_adapter *envctrl_adap;
+extern spinlock_t envctrl_lock;
+
+/* returns 0 on success, error code on failure */
+int __env2_i2c_readb(u8 addr, u8 *rv)
+{ 
+	int ret;
+	struct i2c_msg msg = {
+		.addr  = (addr>>1),
+		.flags = I2C_M_RD,
+		.len   = 1,
+		.buf   = rv,
+	};
+
+	ret = i2c_transfer(envctrl_adap, &msg, 1);
+	if (ret == 1) {
+		ret = 0;
+	}
+	return (ret == 1) ? 0 : ret;
+}
+
+int env2_i2c_readb(u8 addr, u8 *rv)
+{ 
+	int ret;
+	unsigned long flags;
+	spin_lock_irqsave(&envctrl_lock, flags);
+
+	ret = __env2_i2c_readb(addr, rv);
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+	return ret;
+}
+
+int __env2_i2c_writeb(u8 addr, u8 val)
+{ 
+	u8 valbuf = val;
+	int ret;
+
+	struct i2c_msg msg = {
+		.addr  = (addr>>1),
+		.flags = 0,
+		.len   = 1,
+		.buf   = &valbuf,
+	};
+
+	if (!envctrl_adap) {
+		BUG();
+	}
+
+	ret = i2c_transfer(envctrl_adap, &msg, 1);
+	if (ret == 1) {
+		ret = 0;
+	}
+	return ret;
+}
+
+int env2_i2c_writeb(u8 addr, u8 val)
+{ 
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&envctrl_lock, flags);
+	ret = __env2_i2c_writeb(addr, val);
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+	return ret;
+}
+
+u8 __envctrl_i2c_read_8574(u8 addr) 
+{
+	u8 buf;
+	(void) __env2_i2c_readb(addr, &buf);
+	return buf;
+}
+u8 envctrl_i2c_read_8574(u8 addr) 
+{
+	u8 buf;
+	(void) env2_i2c_readb(addr, &buf);
+	return buf;
+}
+
+int __envctrl_i2c_read_8591(u8 addr, u8 port, u8 *rv)
+{
+#define PCF8591_AOE (0x40)
+	int ret;
+	u8 tmp = port | PCF8591_AOE;
+
+	struct i2c_msg msg[] = {
+		{
+			.addr  = (addr>>1),
+			.flags = 0,
+			.len   = 1,
+			.buf   = &tmp,
+		},
+		{
+			/* dummy read-- result of previous conversion cycle */
+			.addr  = (addr>>1),
+			.flags = I2C_M_RD,
+			.len   = 1,
+			.buf   = &tmp,
+		},
+		{
+			.addr  = (addr>>1),
+			.flags = I2C_M_RD,
+			.len   = 1,
+			.buf   = rv,
+		},
+	
+	};
+
+	int msgcnt = sizeof(msg) / sizeof(struct i2c_msg);
+	if (!envctrl_adap) {
+		BUG();
+	}
+	ret = i2c_transfer(envctrl_adap, msg, msgcnt);
+	if (ret == msgcnt) {
+		ret = 0;
+	}
+	return ret;
+}
+
+int envctrl_i2c_read_8591(u8 addr, u8 port, u8 *rv)
+{
+	int ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&envctrl_lock, flags);
+	ret = __envctrl_i2c_read_8591(addr, port, rv);
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+	return ret;
+}
+
+u8 __envctrl_i2c_write_8591(u8 addr, u8 data)
+{
+	int ret;
+	u8 tmp[] = { PCF8591_AOE, data };
+
+	struct i2c_msg msg[] = {
+		{
+			.addr  = (addr>>1),
+			.flags = 0,
+			.len   = 2,
+			.buf   = tmp,
+		}
+	};
+
+	int msgcnt = sizeof(msg) / sizeof(struct i2c_msg);
+
+	if (!envctrl_adap) {
+		BUG();
+	}
+	ret = i2c_transfer(envctrl_adap, msg, msgcnt);
+	if (ret == msgcnt) {
+		ret = 0;
+	}
+	return ret;
+}
+
+u8 envctrl_i2c_write_8591(u8 addr, u8 data)
+{
+	u8 ret;
+	unsigned long flags;
+
+	spin_lock_irqsave(&envctrl_lock, flags);
+	ret = __envctrl_i2c_write_8591(addr, data);
+	spin_unlock_irqrestore(&envctrl_lock, flags);
+	return ret;
+}
===== arch/sparc64/kernel/env_lib.h 1.1 vs edited =====
--- 1.1/arch/sparc64/kernel/env_lib.h	2005-02-14 23:56:01 -08:00
+++ edited/arch/sparc64/kernel/env_lib.h	2005-02-15 08:45:01 -08:00
@@ -0,0 +1,54 @@
+/* $Id$
+ * env_lib.c: Environmental monitoring library code for 
+ *            accessing I2C clients (chips) via i2c-envctrl 
+ *            PCF8584 bus nexus interface 
+ *
+ * Copyright (C) 2004-2005 Eric Brower (ebrower@gmail.com)
+ * 
+ * TODO: functions should all return explicit error codes
+ */
+
+#ifndef _SPARC64_ENV_LIB_H
+#define _SPARC64_ENV_LIB_H
+
+#include <asm/io.h>
+#include <linux/types.h>
+#include <linux/i2c.h>
+
+/* 
+ * "chip" access functions--
+ * We use spinlocks for two reasons:
+ *  1) because the i2c core doesn't support use while in_interrupt()
+ *     so we "wrap" their semaphores with spinlocks (ugh!)
+ *  2) to prevent concurrent access to bus from interrupts or timers
+ *
+ * Unlocked routines are provided for when multiple I2C message
+ * atomicity is required-- the i2c core does not currently allow us
+ * to issue multiple messages with dynamic content (i.e. read,xor,write).
+ * When using unlocked functions (__xxx() funcs) you must acquire and
+ * release the spinlock yourself-- this gives us a level of atomicity
+ * but does not address multi-master concerns because all such transactions
+ * are done as separate START/STOP sequences (FIXME in i2c core).
+ */
+
+/* returns 0 on success, error code on failure */
+int __env2_i2c_readb(u8 addr, u8 *rv);
+int env2_i2c_readb(u8 addr, u8 *rv);
+
+int __env2_i2c_writeb(u8 addr, u8 val);
+int env2_i2c_writeb(u8 addr, u8 val);
+
+/* XXX Temporary-- should be lib code or somesuch */
+#define __envctrl_i2c_write_8574(addr,data) __env2_i2c_writeb(addr,data);
+#define envctrl_i2c_write_8574(addr,data) env2_i2c_writeb(addr,data);
+
+u8 __envctrl_i2c_read_8574(u8 addr);
+u8 envctrl_i2c_read_8574(u8 addr);
+
+int __envctrl_i2c_read_8591(u8 addr, u8 port, u8 *rv);
+int envctrl_i2c_read_8591(u8 addr, u8 port, u8 *rv);
+
+u8 __envctrl_i2c_write_8591(u8 addr, u8 data);
+u8 envctrl_i2c_write_8591(u8 addr, u8 data);
+
+#endif /* #ifndef _SPARC64_ENV_LIB_H */
===== drivers/i2c/algos/i2c-algo-pcf.c 1.16 vs edited =====
--- 1.16/drivers/i2c/algos/i2c-algo-pcf.c	2004-12-07 02:14:58 -08:00
+++ edited/drivers/i2c/algos/i2c-algo-pcf.c	2005-02-11 17:50:20 -08:00
@@ -78,7 +78,6 @@
 	set_pcf(adap, 1, I2C_PCF_STOP);
 }
 
-
 static int wait_for_bb(struct i2c_algo_pcf_data *adap) {
 
 	int timeout = DEF_TIMEOUT;
@@ -109,6 +108,26 @@
 		adap->waitforpin();
 		*status = get_pcf(adap, 1);
 	}
+	if (*status & I2C_PCF_LAB) {
+		DEB2(printk(KERN_INFO 
+			"i2c-algo-pcf.o: lost arbitration (CSR 0x%02x)\n",
+			 *status));
+		/* Cleanup from LAB-- reset and enable ESO.
+		 * This resets the PCF8584; since we've lost the bus, no
+		 * further attempts should be made by callers to clean up 
+		 * (no i2c_stop() etc.)
+		 */
+		set_pcf(adap, 1, I2C_PCF_PIN);
+		set_pcf(adap, 1, I2C_PCF_ESO);
+		/* TODO: we should pause for a time period sufficient for any
+		 * running I2C transaction to complete-- the arbitration
+		 * logic won't work properly until the next START is seen.
+		 */
+		DEB2(printk(KERN_INFO 
+			"i2c-algo-pcf.o: reset LAB condition (CSR 0x%02x)\n", 
+			get_pcf(adap,1)));
+		return(-EINTR);
+	}
 #endif
 	if (timeout <= 0)
 		return(-1);
@@ -188,16 +207,22 @@
 		       unsigned char addr, int retries)
 {
 	int i, status, ret = -1;
+	int wfp;
 	for (i=0;i<retries;i++) {
 		i2c_outb(adap, addr);
 		i2c_start(adap);
 		status = get_pcf(adap, 1);
-		if (wait_for_pin(adap, &status) >= 0) {
+		if ((wfp = wait_for_pin(adap, &status)) >= 0) {
 			if ((status & I2C_PCF_LRB) == 0) { 
 				i2c_stop(adap);
 				break;	/* success! */
 			}
 		}
+		if (wfp == -EINTR) {
+			/* arbitration lost */
+			udelay(adap->udelay);
+			return -EINTR;
+		}
 		i2c_stop(adap);
 		udelay(adap->udelay);
 	}
@@ -219,6 +244,10 @@
 		i2c_outb(adap, buf[wrcount]);
 		timeout = wait_for_pin(adap, &status);
 		if (timeout) {
+			if (timeout == -EINTR) {
+				/* arbitration lost */
+				return -EINTR;
+			}
 			i2c_stop(adap);
 			dev_err(&i2c_adap->dev, "i2c_write: error - timeout.\n");
 			return -EREMOTEIO; /* got a better one ?? */
@@ -247,11 +276,16 @@
 {
 	int i, status;
 	struct i2c_algo_pcf_data *adap = i2c_adap->algo_data;
+	int wfp;
 
 	/* increment number of bytes to read by one -- read dummy byte */
 	for (i = 0; i <= count; i++) {
 
-		if (wait_for_pin(adap, &status)) {
+		if ((wfp = wait_for_pin(adap, &status))) {
+			if (wfp == -EINTR) {
+				/* arbitration lost */
+				return -EINTR;
+			}
 			i2c_stop(adap);
 			dev_err(&i2c_adap->dev, "pcf_readbytes timed out.\n");
 			return (-1);
@@ -366,6 +400,10 @@
 		/* Wait for PIN (pending interrupt NOT) */
 		timeout = wait_for_pin(adap, &status);
 		if (timeout) {
+			if (timeout == -EINTR) {
+				/* arbitration lost */
+				return (-EINTR);
+			}
 			i2c_stop(adap);
 			DEB2(printk(KERN_ERR "i2c-algo-pcf.o: Timeout waiting "
 				    "for PIN(1) in pcf_xfer\n");)
===== drivers/i2c/busses/Kconfig 1.67 vs edited =====
--- 1.67/drivers/i2c/busses/Kconfig	2005-01-17 12:30:41 -08:00
+++ edited/drivers/i2c/busses/Kconfig	2005-02-02 18:16:43 -08:00
@@ -476,4 +476,16 @@
 	  This driver can also be built as a module.  If so, the module
 	  will be called i2c-pca-isa.
 
+config I2C_ENVCTRL
+	tristate "Sun Microsystems envctrl I2C bus nexus driver"
+	depends on SPARC64 && PCI
+	select I2C_ALGOPCF
+	help
+	  This driver provides access to the PCF8584 I2C bus nexus
+	  used for environmental monitoring on certain Sun Microsystems
+	  platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called i2c-envctrl.
+
 endmenu
===== drivers/i2c/busses/Makefile 1.40 vs edited =====
--- 1.40/drivers/i2c/busses/Makefile	2004-11-18 20:39:29 -08:00
+++ edited/drivers/i2c/busses/Makefile	2005-02-02 18:13:43 -08:00
@@ -9,6 +9,7 @@
 obj-$(CONFIG_I2C_AMD756_S4882)	+= i2c-amd756-s4882.o
 obj-$(CONFIG_I2C_AMD8111)	+= i2c-amd8111.o
 obj-$(CONFIG_I2C_ELEKTOR)	+= i2c-elektor.o
+obj-$(CONFIG_I2C_ENVCTRL)	+= i2c-envctrl.o
 obj-$(CONFIG_I2C_HYDRA)		+= i2c-hydra.o
 obj-$(CONFIG_I2C_I801)		+= i2c-i801.o
 obj-$(CONFIG_I2C_I810)		+= i2c-i810.o
===== drivers/i2c/busses/i2c-envctrl.c 1.1 vs edited =====
--- 1.1/drivers/i2c/busses/i2c-envctrl.c	2005-02-03 10:06:36 -08:00
+++ edited/drivers/i2c/busses/i2c-envctrl.c	2005-02-03 10:06:58 -08:00
@@ -0,0 +1,254 @@
+/* i2c-envctrl - PCF8584 I2C bus nexus driver for supported 
+ *               Sun Microsystems systems
+ * 
+ * Copyright (c) 2004-2005 Eric Brower <ebrower@gmail.com>
+ *
+ * Adapted from i2c-elektor.c - reference that file for copyright information
+ */
+
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/ioport.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/wait.h>
+
+#include <linux/i2c.h>
+#include <linux/i2c-algo-pcf.h>
+
+#include <asm/ebus.h>
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/i2c-envctrl.h>
+
+#include "../algos/i2c-algo-pcf.h"
+
+struct pcf8584_regs {
+	volatile u8 data;
+	volatile u8 csr;
+};
+
+static struct __iomem pcf8584_regs *i2c;
+static int irq;
+static int clock  = 0x1c;
+static int own    = 0x55;
+
+static wait_queue_head_t pcf_wait;
+static int pcf_pending;
+static spinlock_t lock;
+
+static struct pcf_envctrl_nexus nexus = { 
+	.type   = TYPE_UNKNOWN, 
+	.i2c_id = -1,
+	.edev   = NULL 
+};
+
+/* ----- local functions ----------------------------------------------	*/
+
+static void pcf_envctrl_setbyte(void *data, int ctl, int val)
+{
+	/* enable irq if any specified for serial operation */
+	if (ctl && irq && (val & I2C_PCF_ESO)) {
+		val |= I2C_PCF_ENI;
+	}
+
+	pr_debug("i2c-envctrl: Write 0x%X 0x%02X\n", address, val & 255);
+
+	if (ctl) {
+		writeb(val, &i2c->csr);
+	}
+	else {
+		writeb(val, &i2c->data);
+	}
+}
+
+static int pcf_envctrl_getbyte(void *data, int ctl)
+{
+	int val;
+
+	if (ctl) {
+		val = readb(&i2c->csr);
+	}
+	else {
+		val = readb(&i2c->data);
+	}
+
+	pr_debug("i2c-envctrl: Read 0x%X 0x%02X\n", address, val);
+
+	return (val);
+}
+
+static int pcf_envctrl_getown(void *data)
+{
+	return (own);
+}
+
+
+static int pcf_envctrl_getclock(void *data)
+{
+	return (clock);
+}
+
+static void pcf_envctrl_waitforpin(void) {
+
+	int timeout = 2;
+	long flags;
+
+	if (irq > 0) {
+		spin_lock_irqsave(&lock, flags);
+		if (pcf_pending == 0) {
+			spin_unlock_irqrestore(&lock, flags);
+			if (interruptible_sleep_on_timeout(&pcf_wait,
+								timeout*HZ)) {
+				spin_lock_irqsave(&lock, flags);
+				if (pcf_pending == 1) {
+					pcf_pending = 0;
+				}
+				spin_unlock_irqrestore(&lock, flags);
+			}
+		} else {
+			pcf_pending = 0;
+			spin_unlock_irqrestore(&lock, flags);
+		}
+	} else {
+		udelay(100);
+	}
+}
+
+
+static irqreturn_t pcf_envctrl_handler(int this_irq, void *dev_id, struct pt_regs *regs) {
+	spin_lock(&lock);
+	pcf_pending = 1;
+	spin_unlock(&lock);
+	wake_up_interruptible(&pcf_wait);
+	return IRQ_HANDLED;
+}
+
+
+static int pcf_envctrl_init(void)
+{
+	spin_lock_init(&lock);
+	if (irq > 0) {
+		if (request_irq(irq, pcf_envctrl_handler, 0, "PCF8584", NULL) < 0) {
+			printk(KERN_ERR "i2c-envctrl: Request irq%d failed\n", irq);
+			irq = 0;
+		} 
+		else {
+			enable_irq(irq);
+		}
+	}
+	return 0;
+}
+
+static struct i2c_algo_pcf_data pcf_envctrl_data = {
+	.setpcf	    = pcf_envctrl_setbyte,
+	.getpcf	    = pcf_envctrl_getbyte,
+	.getown	    = pcf_envctrl_getown,
+	.getclock   = pcf_envctrl_getclock,
+	.waitforpin = pcf_envctrl_waitforpin,
+	.udelay	    = 10,
+	.mdelay	    = 10,
+	.timeout    = 100,
+};
+
+static struct i2c_adapter pcf_envctrl_ops = {
+	.owner		= THIS_MODULE,
+	.id		= I2C_HW_P_ELEK,
+	.algo_data	= &pcf_envctrl_data,
+	.name		= "PCF8584 bus nexus adapter",
+};
+
+static int __init i2c_envctrl_init(void) 
+{
+	struct linux_ebus *ebus = NULL;
+	struct linux_ebus_device *edev = NULL;
+                                                                                
+	for_each_ebus(ebus) {
+		for_each_ebusdev(edev, ebus) {
+			/* TODO: change this to operate off array */
+			if (!strcmp(edev->prom_name, "SUNW,envctrltwo")) {
+				nexus.type = TYPE_ENVCTRLTWO;
+				goto done;
+			}
+			else if (!strcmp(edev->prom_name, "SUNW,envctrl")) {
+				nexus.type = TYPE_ENVCTRL;
+				goto done;
+			}
+			else if (!strcmp(edev->prom_name, "SUNW,rasctrl")) {
+				nexus.type = TYPE_RASCTRL;
+				goto done;
+			}
+			else if (!strcmp(edev->prom_name, "i2c")) {
+				nexus.type = TYPE_I2C;
+				goto done;
+			}
+		}
+	}
+done:
+	if (!edev) {
+		return -ENODEV;
+	}
+	nexus.edev = edev;
+
+	i2c = ioremap(edev->resource[0].start, sizeof(struct pcf8584_regs));
+	if (!i2c) {
+		return -EINVAL;
+	}
+
+	init_waitqueue_head(&pcf_wait);
+
+	if (pcf_envctrl_init())
+		return -ENODEV;
+
+	if ((nexus.i2c_id = i2c_pcf_add_bus(&pcf_envctrl_ops)) < 0)
+		goto fail;
+
+	printk(KERN_INFO "i2c-envctrl: %s PCF8584 bus nexus at 0x%lx\n",
+		edev->prom_name, (unsigned long)i2c);
+	
+	return 0;
+
+ fail:
+	if (irq > 0) {
+		disable_irq(irq);
+		free_irq(irq, NULL);
+	}
+
+	return -ENODEV;
+}
+
+static void i2c_envctrl_exit(void)
+{
+	i2c_pcf_del_bus(&pcf_envctrl_ops);
+
+	if (irq > 0) {
+		disable_irq(irq);
+		free_irq(irq, NULL);
+	}
+
+	if (i2c) {
+		iounmap(i2c);
+	}
+}
+
+struct pcf_envctrl_nexus * pcf_envctrl_get_nexus(void)
+{
+	return &nexus;
+}
+
+EXPORT_SYMBOL(pcf_envctrl_get_nexus);
+
+MODULE_AUTHOR("Eric Brower <ebrower@gmail.com>");
+MODULE_DESCRIPTION("PCF8584 I2C bus nexus driver for Sun Microsystems envctrl");
+MODULE_LICENSE("GPL");
+
+module_param(irq, int, 0);
+module_param(clock, int, 0);
+module_param(own, int, 0);
+
+module_init(i2c_envctrl_init);
+module_exit(i2c_envctrl_exit);
===== include/asm-sparc64/i2c-envctrl.h 1.1 vs edited =====
--- 1.1/include/asm-sparc64/i2c-envctrl.h	2005-02-03 10:10:38 -08:00
+++ edited/include/asm-sparc64/i2c-envctrl.h	2005-02-03 10:10:56 -08:00
@@ -0,0 +1,21 @@
+#ifndef _SPARC64_I2C_ENVCTRL
+#define _SPARC64_I2C_ENVCTRL
+
+enum pcf_envctrl_types {
+	TYPE_UNKNOWN = 0,
+	TYPE_ENVCTRL = 1,
+	TYPE_ENVCTRLTWO,
+	TYPE_RASCTRL,
+	TYPE_I2C,
+};
+typedef enum pcf_envctrl_types pcf_envctrl_type_t;
+
+struct pcf_envctrl_nexus {
+	pcf_envctrl_type_t	 type;
+	int			 i2c_id;
+	struct linux_ebus_device *edev;
+};
+
+extern struct pcf_envctrl_nexus * pcf_envctrl_get_nexus(void);
+
+#endif /* _SPARC64_I2C_ENVCTRL */
