===== 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#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 +#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 +#include +#include + +/* + * "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= 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 + * + * Adapted from i2c-elektor.c - reference that file for copyright information + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#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 "); +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 */