diff --git a/CHANGES b/CHANGES index 4bd32876..95e5e99b 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,7 @@ SVN HEAD Add applesmc support Man page sensors.conf.5: Update the chip statement section Module i2c-nforce2: Add nForce MCP61, MCP65 support (2.6 backport) + Module w83627ehf: New (2.6 backport) Programs i2cdump, i2cget, i2cset: Do not force the slave address by default Program i2cdump: Use the new I2C block read function Program isadump: Detect when address bit 7 is a busy flag @@ -22,6 +23,7 @@ SVN HEAD Better integration with init scripts Program pwmconfig: Use smaller steps for low PWM values Support optional min and max PWM values + Program mkpatch: Include w83627ehf Program rrd: Support non-i2c devices (hwmon class) Add missing shell declarations Update URI diff --git a/README b/README index c8fc488b..e2584cf1 100644 --- a/README +++ b/README @@ -89,7 +89,8 @@ At least the following hardware sensor chips are supported: TI THMC10 and THMC50 VIA Technologies VT1211, VT8231 and VT82C686A/B embedded sensors Winbond W83781D, W83782D, W83783S, W83791D, W83792D, - W83627HF, W83627THF, W83637HF, W83687THF and W83697HF + W83627HF, W83627THF, W83637HF, W83687THF, W83697HF, + W83627EHF/EHG We also support some miscellaneous chips: diff --git a/doc/chips/SUMMARY b/doc/chips/SUMMARY index ca5afe4e..8c7d2f13 100644 --- a/doc/chips/SUMMARY +++ b/doc/chips/SUMMARY @@ -244,6 +244,9 @@ w83627hf w83687thf 3 7 3 3 pw/da no yes (LPC) w83697hf 2 8 2 2 pwm no yes (LPC) +w83627ehf + w83627ehf 3 10 3-5 4 pwm no yes (LPC) + w83781d as99127f 3 7 3 - yes no w83781d 3 7 3 - yes yes diff --git a/doc/chips/w83627ehf b/doc/chips/w83627ehf new file mode 100644 index 00000000..248f392c --- /dev/null +++ b/doc/chips/w83627ehf @@ -0,0 +1,67 @@ +Kernel driver `w83627ehf.o' +=========================== + +Status: Stable; some missing features. + +Supported chips: + * Winbond W83627EHF/EHG (ISA access ONLY) + Prefix: 'w83627ehf' + Addresses scanned: ISA address retrieved from Super I/O registers + Datasheet: http://www.winbond-usa.com/products/winbond_products/pdfs/PCIC/W83627EHF_%20W83627EHGb.pdf + +Authors: + Jean Delvare + Yuan Mu (Winbond) + Rudolf Marek + David Hubbard + +Thanks to Observit and Bernardo Motta for providing a motherboard, CPU +and memory that made it possible to backport the w83627ehf driver from +Linux 2.6 to Linux 2.4. + + +Description +----------- + +This driver implements support for the Winbond W83627EHF and W83627EHG +super I/O chips. Support for the W83627DHG isn't included, but could be +easily added if needed. + +The chips implement three temperature sensors, five fan rotation speed +sensors, ten analog voltage sensors, alarms with beep warnings (control +unimplemented), some automatic fan regulation strategies (unimplemented) +and manual fan speed control. + +Temperatures are measured in degrees Celsius and measurement resolution is +1 degree Celsius for temp1 and 0.5 degee Celsius for temp2 and temp3. An +alarm is triggered when the temperature gets higher than high limit; it +stays on until the temperature falls below the hysteresis value. + +Fan rotation speeds are reported in RPM (rotations per minute). An alarm is +triggered if the rotation speed has dropped below a programmable limit. Fan +readings can be divided by a programmable divider (1, 2, 4, 8, 16, 32, 64 or +128) to give the readings more range or accuracy. The driver sets the most +suitable fan divisor itself. Some fans might not be present because they +share pins with other functions. + +Voltage sensors (also known as IN sensors) report their values in Volts. +An alarm is triggered if the voltage has crossed a programmable minimum +or maximum limit. + + +Fan speed control +----------------- + +The chips support 4 different fan speed control modes. The currently +selected mode can be read from (and written to) the second value of +files pwm[1-4]. Modes are: + + 1: Manual mode. The desired fan speed can be written to the first + value of the pwm[1-4] file, from 0 (stop) to 255 (full speed). + 2: Thermal cruise mode (unimplemented). + 3: Fan speed cuise mode (unimplemented). + 4: Smart Fan III mode (pwm2 and pwm4 only, unimplemented). + +While the automatic modes are not implemented by this driver, they can +still be selected, so they will work if (and only if) the BIOS has +programmed the chip with sensible register values. diff --git a/kernel/chips/Module.mk b/kernel/chips/Module.mk index f8a7485a..f84cad2f 100644 --- a/kernel/chips/Module.mk +++ b/kernel/chips/Module.mk @@ -176,6 +176,9 @@ endif ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83627HF=y' $(LINUX)/.config; then echo 1; fi),1) KERNELCHIPSTARGETS += $(MODULE_DIR)/w83627hf.o endif +ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83627EHF=y' $(LINUX)/.config; then echo 1; fi),1) +KERNELCHIPSTARGETS += $(MODULE_DIR)/w83627ehf.o +endif ifneq ($(shell if grep -q '^CONFIG_SENSORS_W83L785TS=y' $(LINUX)/.config; then echo 1; fi),1) KERNELCHIPSTARGETS += $(MODULE_DIR)/w83l785ts.o endif diff --git a/kernel/chips/w83627ehf.c b/kernel/chips/w83627ehf.c new file mode 100644 index 00000000..24e2bb8f --- /dev/null +++ b/kernel/chips/w83627ehf.c @@ -0,0 +1,1013 @@ +/* + w83627ehf - Driver for the hardware monitoring functionality of + the Winbond W83627EHF Super-I/O chip + Copyright (C) 2005, 2007 Jean Delvare + Copyright (C) 2006 Rudolf Marek + Backported to kernel 2.4 by Yuan Mu (Winbond). + + Shamelessly ripped from the w83627hf driver + Copyright (C) 2003 Mark Studebaker + + This driver also supports the W83627EHG, which is the lead-free + version of the W83627EHF. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + Supports the following chips: + + Chip #vin #fan #pwm #temp + w83627ehf 10 5 4 3 +*/ + +#include +#include +#include +#include +#include +#include +#include +#include "version.h" +#include "lm75.h" + +#define DEBUG 1 + +/* The actual ISA address is read from Super-I/O configuration space */ +static unsigned short normal_i2c[] = { SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { 0, SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod parameters */ +SENSORS_INSMOD_1(w83627ehf); + +/* + * Super-I/O constants and functions + */ + +static int REG; /* The register to read/write */ +static int VAL; /* The value to read/write */ + +#define W83627EHF_LD_HWM 0x0b + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define SIO_W83627EHF_ID 0x8850 +#define SIO_W83627EHG_ID 0x8860 +#define SIO_ID_MASK 0xFFF0 + +static inline void superio_outb(int reg, int val) +{ + outb(reg, REG); + outb(val, VAL); +} + +static inline int superio_inb(int reg) +{ + outb(reg, REG); + return inb(VAL); +} + +static inline void superio_select(int ld) +{ + outb(SIO_REG_LDSEL, REG); + outb(ld, VAL); +} + +static inline void superio_enter(void) +{ + outb(0x87, REG); + outb(0x87, REG); +} + +static inline void superio_exit(void) +{ + outb(0x02, REG); + outb(0x02, VAL); +} + +/* + * ISA constants + */ + +#define REGION_ALIGNMENT ~7 +#define REGION_OFFSET 5 +#define REGION_LENGTH 2 +#define ADDR_REG_OFFSET 5 +#define DATA_REG_OFFSET 6 + +#define W83627EHF_REG_BANK 0x4E +#define W83627EHF_REG_CONFIG 0x40 + +static const u16 W83627EHF_REG_FAN[] = { 0x28, 0x29, 0x2a, 0x3f, 0x553 }; +static const u16 W83627EHF_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d, 0x3e, 0x55c }; + +/* The W83627EHF registers for nr=7,8,9 are in bank 5 */ +#define W83627EHF_REG_IN_MAX(nr) ((nr < 7) ? (0x2b + (nr) * 2) : \ + (0x554 + (((nr) - 7) * 2))) +#define W83627EHF_REG_IN_MIN(nr) ((nr < 7) ? (0x2c + (nr) * 2) : \ + (0x555 + (((nr) - 7) * 2))) +#define W83627EHF_REG_IN(nr) ((nr < 7) ? (0x20 + (nr)) : \ + (0x550 + (nr) - 7)) + +static const u16 W83627EHF_REG_TEMP[] = { 0x27, 0x150, 0x250 }; +static const u16 W83627EHF_REG_TEMP_HYST[] = { 0x3a, 0x153, 0x253 }; +static const u16 W83627EHF_REG_TEMP_OVER[] = { 0x39, 0x155, 0x255 }; +static const u16 W83627EHF_REG_TEMP_CONFIG[] = { 0x152, 0x252 }; + +/* Fan clock dividers are spread over the following five registers */ +#define W83627EHF_REG_FANDIV1 0x47 +#define W83627EHF_REG_FANDIV2 0x4B +#define W83627EHF_REG_VBAT 0x5D +#define W83627EHF_REG_DIODE 0x59 +#define W83627EHF_REG_SMI_OVT 0x4C + +#define W83627EHF_REG_ALARM1 0x459 +#define W83627EHF_REG_ALARM2 0x45A +#define W83627EHF_REG_ALARM3 0x45B + +/* SmartFan registers */ +/* DC or PWM output fan configuration */ +static const u8 W83627EHF_REG_PWM_ENABLE[] = { + 0x04, /* SYS FAN0 */ + 0x04, /* CPU FAN0 */ + 0x12, /* AUX FAN */ + 0x62, /* CPU FAN1 */ +}; + +#if 0 +static const u8 W83627EHF_PWM_MODE_SHIFT[4] = { 0, 1, 0, 6 }; +#endif +static const u8 W83627EHF_PWM_ENABLE_SHIFT[4] = { 2, 4, 1, 4 }; + +/* FAN Duty Cycle, be used to control */ +static const u8 W83627EHF_REG_PWM[] = { 0x01, 0x03, 0x11, 0x61 }; + +/* + * Conversions + */ + +static inline unsigned int fan_from_reg(u8 reg, unsigned int div) +{ + if (reg == 0 || reg == 255) + return 0; + return 1350000U / (reg * div); +} + +static inline unsigned int div_from_reg(u8 reg) +{ + return 1 << reg; +} + +/* Some of analog inputs have internal scaling (2x), 8mV is ADC LSB */ +static const u8 scale_in[10] = { 8, 8, 16, 16, 8, 8, 8, 16, 16, 8 }; + +static inline long in_from_reg(u8 reg, u8 nr) +{ + return reg * scale_in[nr]; +} + +static inline u8 in_to_reg(long val, u8 nr) +{ + return SENSORS_LIMIT((val + scale_in[nr] / 2) / scale_in[nr], 0, 255); +} + +/* + * Data structures and manipulation thereof + */ + +struct w83627ehf_data { + struct i2c_client client; + struct class_device *class_dev; + int sysctl_id; + struct semaphore lock; + struct semaphore update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values */ + u8 in[10]; /* Register value */ + u8 in_max[10]; /* Register value */ + u8 in_min[10]; /* Register value */ + u8 fan[5]; + u8 fan_min[5]; + u8 fan_div[5]; + u8 has_fan; /* some fan inputs can be disabled */ + u16 temp[3]; + u16 temp_max[3]; + u16 temp_hyst[3]; + u32 alarms; + + u8 pwm_enable[4]; /* 1 for manual, 2+ for auto */ + u8 pwm[4]; +}; + +/* The /proc/sys entries */ +/* -- SENSORS SYSCTL START -- */ + +#define W83627EHF_SYSCTL_IN0 1000 /* Volts * 1000 */ +#define W83627EHF_SYSCTL_IN1 1001 +#define W83627EHF_SYSCTL_IN2 1002 +#define W83627EHF_SYSCTL_IN3 1003 +#define W83627EHF_SYSCTL_IN4 1004 +#define W83627EHF_SYSCTL_IN5 1005 +#define W83627EHF_SYSCTL_IN6 1006 +#define W83627EHF_SYSCTL_IN7 1007 +#define W83627EHF_SYSCTL_IN8 1008 +#define W83627EHF_SYSCTL_IN9 1009 +#define W83627EHF_SYSCTL_FAN1 1101 /* Rotations/min */ +#define W83627EHF_SYSCTL_FAN2 1102 +#define W83627EHF_SYSCTL_FAN3 1103 +#define W83627EHF_SYSCTL_FAN4 1104 +#define W83627EHF_SYSCTL_FAN5 1105 +#define W83627EHF_SYSCTL_TEMP1 1201 /* Degrees Celsius * 10 */ +#define W83627EHF_SYSCTL_TEMP2 1202 +#define W83627EHF_SYSCTL_TEMP3 1203 +#define W83627EHF_SYSCTL_PWM1 1401 +#define W83627EHF_SYSCTL_PWM2 1402 +#define W83627EHF_SYSCTL_PWM3 1403 +#define W83627EHF_SYSCTL_PWM4 1404 +#define W83627EHF_SYSCTL_FAN_DIV 1506 +#define W83627EHF_SYSCTL_ALARMS 1507 /* bitvector */ + +#define W83627EHF_ALARM_IN0 (1 << 0) +#define W83627EHF_ALARM_IN1 (1 << 1) +#define W83627EHF_ALARM_IN2 (1 << 2) +#define W83627EHF_ALARM_IN3 (1 << 3) +#define W83627EHF_ALARM_IN4 (1 << 8) +#define W83627EHF_ALARM_IN5 (1 << 21) +#define W83627EHF_ALARM_IN6 (1 << 20) +#define W83627EHF_ALARM_IN7 (1 << 16) +#define W83627EHF_ALARM_IN8 (1 << 17) +#define W83627EHF_ALARM_IN9 (1 << 19) +#define W83627EHF_ALARM_TEMP1 (1 << 4) +#define W83627EHF_ALARM_TEMP2 (1 << 5) +#define W83627EHF_ALARM_TEMP3 (1 << 13) +#define W83627EHF_ALARM_FAN1 (1 << 6) +#define W83627EHF_ALARM_FAN2 (1 << 7) +#define W83627EHF_ALARM_FAN3 (1 << 11) +#define W83627EHF_ALARM_FAN4 (1 << 10) +#define W83627EHF_ALARM_FAN5 (1 << 23) + +/* -- SENSORS SYSCTL END -- */ + +static inline int is_word_sized(u16 reg) +{ + return (((reg & 0xff00) == 0x100 || (reg & 0xff00) == 0x200) + && ((reg & 0x00ff) == 0x50 || (reg & 0x00ff) == 0x53 || + (reg & 0x00ff) == 0x55)); +} + +/* We assume that the default bank is 0, thus the following two functions do + nothing for registers which live in bank 0. For others, they respectively + set the bank register to the correct value (before the register is + accessed), and back to 0 (afterwards). */ +static inline void w83627ehf_set_bank(struct i2c_client *client, u16 reg) +{ + if (reg & 0xff00) { + outb_p(W83627EHF_REG_BANK, client->addr + ADDR_REG_OFFSET); + outb_p(reg >> 8, client->addr + DATA_REG_OFFSET); + } +} + +static inline void w83627ehf_reset_bank(struct i2c_client *client, u16 reg) +{ + if (reg & 0xff00) { + outb_p(W83627EHF_REG_BANK, client->addr + ADDR_REG_OFFSET); + outb_p(0, client->addr + DATA_REG_OFFSET); + } +} + +static u16 w83627ehf_read_value(struct i2c_client *client, u16 reg) +{ + struct w83627ehf_data *data = client->data; + int res, word_sized = is_word_sized(reg); + + down(&data->lock); + + w83627ehf_set_bank(client, reg); + outb_p(reg & 0xff, client->addr + ADDR_REG_OFFSET); + res = inb_p(client->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, client->addr + ADDR_REG_OFFSET); + res = (res << 8) + inb_p(client->addr + DATA_REG_OFFSET); + } + w83627ehf_reset_bank(client, reg); + + up(&data->lock); + return res; +} + +static int w83627ehf_write_value(struct i2c_client *client, u16 reg, u16 value) +{ + struct w83627ehf_data *data = client->data; + int word_sized = is_word_sized(reg); + + down(&data->lock); + + w83627ehf_set_bank(client, reg); + outb_p(reg & 0xff, client->addr + ADDR_REG_OFFSET); + if (word_sized) { + outb_p(value >> 8, client->addr + DATA_REG_OFFSET); + outb_p((reg & 0xff) + 1, client->addr + ADDR_REG_OFFSET); + } + outb_p(value & 0xff, client->addr + DATA_REG_OFFSET); + w83627ehf_reset_bank(client, reg); + + up(&data->lock); + return 0; +} + +/* This function assumes that the caller holds data->update_lock */ +static void w83627ehf_write_fan_div(struct i2c_client *client, int nr) +{ + struct w83627ehf_data *data = client->data; + u8 reg; + + switch (nr) { + case 0: + reg = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1) + & 0xcf; + reg |= (data->fan_div[0] & 0x03) << 4; + /* fan5 input control bit is write only, compute the value */ + reg |= (data->has_fan & (1 << 4)) ? 1 : 0; + w83627ehf_write_value(client, W83627EHF_REG_FANDIV1, reg); + reg = w83627ehf_read_value(client, W83627EHF_REG_VBAT) + & 0xdf; + reg |= (data->fan_div[0] & 0x04) << 3; + w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + break; + case 1: + reg = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1) + & 0x3f; + reg |= (data->fan_div[1] & 0x03) << 6; + /* fan5 input control bit is write only, compute the value */ + reg |= (data->has_fan & (1 << 4)) ? 1 : 0; + w83627ehf_write_value(client, W83627EHF_REG_FANDIV1, reg); + reg = w83627ehf_read_value(client, W83627EHF_REG_VBAT) + & 0xbf; + reg |= (data->fan_div[1] & 0x04) << 4; + w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + break; + case 2: + reg = w83627ehf_read_value(client, W83627EHF_REG_FANDIV2) + & 0x3f; + reg |= (data->fan_div[2] & 0x03) << 6; + w83627ehf_write_value(client, W83627EHF_REG_FANDIV2, reg); + reg = w83627ehf_read_value(client, W83627EHF_REG_VBAT) + & 0x7f; + reg |= (data->fan_div[2] & 0x04) << 5; + w83627ehf_write_value(client, W83627EHF_REG_VBAT, reg); + break; + case 3: + reg = w83627ehf_read_value(client, W83627EHF_REG_DIODE) + & 0xfc; + reg |= data->fan_div[3] & 0x03; + w83627ehf_write_value(client, W83627EHF_REG_DIODE, reg); + reg = w83627ehf_read_value(client, W83627EHF_REG_SMI_OVT) + & 0x7f; + reg |= (data->fan_div[3] & 0x04) << 5; + w83627ehf_write_value(client, W83627EHF_REG_SMI_OVT, reg); + break; + case 4: + reg = w83627ehf_read_value(client, W83627EHF_REG_DIODE) + & 0x73; + reg |= (data->fan_div[4] & 0x03) << 2; + reg |= (data->fan_div[4] & 0x04) << 5; + w83627ehf_write_value(client, W83627EHF_REG_DIODE, reg); + break; + } +} + +static void w83627ehf_update_client(struct i2c_client *client) +{ + struct w83627ehf_data *data = client->data; + int pwmcfg = 0; /* shut up the compiler */ + int i; + + down(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) + || !data->valid) { + /* Fan clock dividers */ + i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1); + data->fan_div[0] = (i >> 4) & 0x03; + data->fan_div[1] = (i >> 6) & 0x03; + i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV2); + data->fan_div[2] = (i >> 6) & 0x03; + i = w83627ehf_read_value(client, W83627EHF_REG_VBAT); + data->fan_div[0] |= (i >> 3) & 0x04; + data->fan_div[1] |= (i >> 4) & 0x04; + data->fan_div[2] |= (i >> 5) & 0x04; + if (data->has_fan & ((1 << 3) | (1 << 4))) { + i = w83627ehf_read_value(client, W83627EHF_REG_DIODE); + data->fan_div[3] = i & 0x03; + data->fan_div[4] = ((i >> 2) & 0x03) + | ((i >> 5) & 0x04); + } + if (data->has_fan & (1 << 3)) { + i = w83627ehf_read_value(client, W83627EHF_REG_SMI_OVT); + data->fan_div[3] |= (i >> 5) & 0x04; + } + + /* Measured voltages and limits */ + for (i = 0; i < 10; i++) { + data->in[i] = w83627ehf_read_value(client, + W83627EHF_REG_IN(i)); + data->in_min[i] = w83627ehf_read_value(client, + W83627EHF_REG_IN_MIN(i)); + data->in_max[i] = w83627ehf_read_value(client, + W83627EHF_REG_IN_MAX(i)); + } + + /* Measured fan speeds and limits */ + for (i = 0; i < 5; i++) { + if (!(data->has_fan & (1 << i))) + continue; + + data->fan[i] = w83627ehf_read_value(client, + W83627EHF_REG_FAN[i]); + data->fan_min[i] = w83627ehf_read_value(client, + W83627EHF_REG_FAN_MIN[i]); + + /* If we failed to measure the fan speed and clock + divider can be increased, let's try that for next + time */ + if (data->fan[i] == 0xff + && data->fan_div[i] < 0x07) { +#ifdef DEBUG + printk(KERN_DEBUG "w83627ehf: Increasing " + "fan%d clock divider from %u to %u\n", + i + 1, div_from_reg(data->fan_div[i]), + div_from_reg(data->fan_div[i] + 1)); +#endif + data->fan_div[i]++; + w83627ehf_write_fan_div(client, i); + /* Preserve min limit if possible */ + if (data->fan_min[i] >= 2 + && data->fan_min[i] != 255) { + w83627ehf_write_value(client, + W83627EHF_REG_FAN_MIN[i], + (data->fan_min[i] /= 2)); + } + } + } + + for (i = 0; i < 4; i++) { + /* pwmcfg mapped for i=0, i=1 to same reg */ + if (i != 1) + pwmcfg = w83627ehf_read_value(client, + W83627EHF_REG_PWM_ENABLE[i]); + + data->pwm_enable[i] = 1 + + ((pwmcfg >> W83627EHF_PWM_ENABLE_SHIFT[i]) & 3); + data->pwm[i] = w83627ehf_read_value(client, + W83627EHF_REG_PWM[i]); + } + + /* Measured temperatures and limits */ + for (i = 0; i < 3; i++) { + data->temp[i] = w83627ehf_read_value(client, + W83627EHF_REG_TEMP[i]); + data->temp_max[i] = w83627ehf_read_value(client, + W83627EHF_REG_TEMP_OVER[i]); + data->temp_hyst[i] = w83627ehf_read_value(client, + W83627EHF_REG_TEMP_HYST[i]); + } + /* temp1 is 8-bit, align it */ + data->temp[0] <<= 8; + data->temp_max[0] <<= 8; + data->temp_hyst[0] <<= 8; + + data->alarms = w83627ehf_read_value(client, + W83627EHF_REG_ALARM1) | + (w83627ehf_read_value(client, + W83627EHF_REG_ALARM2) << 8) | + (w83627ehf_read_value(client, W83627EHF_REG_ALARM3) << 16); + + data->last_updated = jiffies; + data->valid = 1; + } + + up(&data->update_lock); +} + +static void w83627ehf_in(struct i2c_client *client, int operation, int ctl_name, + int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + int nr = ctl_name - W83627EHF_SYSCTL_IN0; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 3; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + results[0] = in_from_reg(data->in_min[nr], nr); + results[1] = in_from_reg(data->in_max[nr], nr); + results[2] = in_from_reg(data->in[nr], nr); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + /* Write low limit into register */ + data->in_min[nr] = in_to_reg(results[0], nr); + w83627ehf_write_value(client, W83627EHF_REG_IN_MIN(nr), + data->in_min[nr]); + } + if (*nrels_mag >= 2) { + /* Write high limit into register */ + data->in_max[nr] = in_to_reg(results[1], nr); + w83627ehf_write_value(client, W83627EHF_REG_IN_MAX(nr), + data->in_max[nr]); + } + up(&data->update_lock); + } +} + +static void store_fan_min(struct i2c_client *client, long val, int nr) +{ + struct w83627ehf_data *data = client->data; + unsigned int reg; + u8 new_div; + + down(&data->update_lock); + if (val <= 0) { + /* No min limit, alarm disabled */ + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; /* No change */ + printk(KERN_INFO "w83627ehf: fan%u low limit and alarm " + "disabled\n", nr + 1); + } else if ((reg = 1350000U / val) >= 128 * 255) { + /* Speed below this value cannot possibly be represented, + even with the highest divider (128) */ + data->fan_min[nr] = 254; + new_div = 7; /* 128 == (1 << 7) */ + printk(KERN_WARNING "w83627ehf: fan%u low limit %lu below " + "minimum %u, set to minimum\n", nr + 1, val, + fan_from_reg(254, 128)); + } else if (!reg) { + /* Speed above this value cannot possibly be represented, + even with the lowest divider (1) */ + data->fan_min[nr] = 1; + new_div = 0; /* 1 == (1 << 0) */ + printk(KERN_WARNING "w83627ehf: fan%u low limit %lu above " + "maximum %u, set to maximum\n", nr + 1, val, + fan_from_reg(1, 1)); + } else { + /* Automatically pick the best divider, i.e. the one such + that the min limit will correspond to a register value + in the 96..192 range */ + new_div = 0; + while (reg > 192 && new_div < 7) { + reg >>= 1; + new_div++; + } + data->fan_min[nr] = reg; + } + + /* Write both the fan clock divider (if it changed) and the new + fan min (unconditionally) */ + if (new_div != data->fan_div[nr]) { + if (new_div > data->fan_div[nr]) + data->fan[nr] >>= (data->fan_div[nr] - new_div); + else + data->fan[nr] <<= (new_div - data->fan_div[nr]); + +#ifdef DEBUG + printk(KERN_DEBUG "w83627ehf: fan%u clock divider changed " + "from %u to %u\n", nr + 1, + div_from_reg(data->fan_div[nr]), + div_from_reg(new_div)); +#endif + data->fan_div[nr] = new_div; + w83627ehf_write_fan_div(client, nr); + } + w83627ehf_write_value(client, W83627EHF_REG_FAN_MIN[nr], + data->fan_min[nr]); + up(&data->update_lock); +} + +static void w83627ehf_fan(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + int nr = ctl_name - W83627EHF_SYSCTL_FAN1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + if (data->has_fan & (1 << nr)) { + results[0] = fan_from_reg(data->fan_min[nr], + div_from_reg(data->fan_div[nr])); + results[1] = fan_from_reg(data->fan[nr], + div_from_reg(data->fan_div[nr])); + } else { + results[0] = 0; + results[1] = 0; + } + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if ((data->has_fan & (1 << nr)) + && (*nrels_mag >= 1)) { + store_fan_min(client, results[0], nr); + } + } +} + +static void w83627ehf_temp(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + int nr = ctl_name - W83627EHF_SYSCTL_TEMP1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 1; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + results[0] = LM75_TEMP_FROM_REG(data->temp_max[nr]); + results[1] = LM75_TEMP_FROM_REG(data->temp_hyst[nr]); + results[2] = LM75_TEMP_FROM_REG(data->temp[nr]); + *nrels_mag = 3; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->temp_max[nr] = LM75_TEMP_TO_REG(results[0]); + w83627ehf_write_value(client, + W83627EHF_REG_TEMP_OVER[nr], + nr ? data->temp_max[nr] : + data->temp_max[nr] >> 8); + } + if (*nrels_mag >= 2) { + data->temp_hyst[nr] = LM75_TEMP_TO_REG(results[1]); + w83627ehf_write_value(client, + W83627EHF_REG_TEMP_HYST[nr], + nr ? data->temp_hyst[nr] : + data->temp_hyst[nr] >> 8); + } + up(&data->update_lock); + } +} + +static void w83627ehf_alarms(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + results[0] = data->alarms; + *nrels_mag = 1; + } +} + +static void w83627ehf_fan_div(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + int i; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + for (i = 0; i < 5; i++) + results[i] = div_from_reg(data->fan_div[i]); + *nrels_mag = 5; + } +} + +/* This function assumes that the caller holds data->update_lock */ +static void store_pwm_enable(struct i2c_client *client, long val, int nr) +{ + struct w83627ehf_data *data = client->data; + u16 reg; + long modemax; + + /* Only pwm2 and pwm4 support Smart Fan III */ + modemax = (nr == 1 || nr == 3) ? 4 : 3; + if (val < 1 || val > modemax) + return; + + data->pwm_enable[nr] = val; + reg = w83627ehf_read_value(client, W83627EHF_REG_PWM_ENABLE[nr]); + reg &= ~(0x03 << W83627EHF_PWM_ENABLE_SHIFT[nr]); + reg |= (val - 1) << W83627EHF_PWM_ENABLE_SHIFT[nr]; + w83627ehf_write_value(client, W83627EHF_REG_PWM_ENABLE[nr], reg); +} + +static void w83627ehf_pwm(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct w83627ehf_data *data = client->data; + int nr = ctl_name - W83627EHF_SYSCTL_PWM1; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + w83627ehf_update_client(client); + results[0] = data->pwm[nr]; + results[1] = data->pwm_enable[nr]; + *nrels_mag = 2; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + down(&data->update_lock); + if (*nrels_mag >= 1) { + data->pwm[nr] = SENSORS_LIMIT(results[0], 0, 255); + w83627ehf_write_value(client, W83627EHF_REG_PWM[nr], + data->pwm[nr]); + } + if (*nrels_mag >= 2) { + store_pwm_enable(client, results[1], nr); + } + up(&data->update_lock); + } +} + +static ctl_table w83627ehf_dir_table_template[] = { + {W83627EHF_SYSCTL_IN0, "in0", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN1, "in1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN2, "in2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN3, "in3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN4, "in4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN5, "in5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN6, "in6", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN7, "in7", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN8, "in8", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + {W83627EHF_SYSCTL_IN9, "in9", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_in}, + + {W83627EHF_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_fan}, + {W83627EHF_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_fan}, + {W83627EHF_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_fan}, + {W83627EHF_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_fan}, + {W83627EHF_SYSCTL_FAN5, "fan5", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_fan}, + + {W83627EHF_SYSCTL_TEMP1, "temp1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_temp}, + {W83627EHF_SYSCTL_TEMP2, "temp2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_temp}, + {W83627EHF_SYSCTL_TEMP3, "temp3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_temp}, + + /* manual adjust fan divisor not allowed */ + {W83627EHF_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &w83627ehf_fan_div}, + {W83627EHF_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_alarms}, + + {W83627EHF_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_pwm}, + {W83627EHF_SYSCTL_PWM2, "pwm2", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_pwm}, + {W83627EHF_SYSCTL_PWM3, "pwm3", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_pwm}, + {W83627EHF_SYSCTL_PWM4, "pwm4", NULL, 0, 0644, NULL, &i2c_proc_real, + &i2c_sysctl_real, NULL, &w83627ehf_pwm}, + {0} +}; + +/* + * Driver and client management + */ + +static struct i2c_driver w83627ehf_driver; + +static void w83627ehf_init_client(struct i2c_client *client) +{ + int i; + u8 tmp; + + /* Start monitoring if needed */ + tmp = w83627ehf_read_value(client, W83627EHF_REG_CONFIG); + if (!(tmp & 0x01)) + w83627ehf_write_value(client, W83627EHF_REG_CONFIG, + tmp | 0x01); + + /* Enable temp2 and temp3 if needed */ + for (i = 0; i < 2; i++) { + tmp = w83627ehf_read_value(client, + W83627EHF_REG_TEMP_CONFIG[i]); + if (tmp & 0x01) + w83627ehf_write_value(client, + W83627EHF_REG_TEMP_CONFIG[i], + tmp & 0xfe); + } + +} + +static int w83627ehf_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + struct i2c_client *client; + struct w83627ehf_data *data; + u8 fan4pin, fan5pin; + int i, err; + + if (!i2c_is_isa_adapter(adapter)) + return -ENODEV; + + if (!request_region(address + REGION_OFFSET, REGION_LENGTH, + "w83627ehf")) { + printk(KERN_ERR "w83627ehf: Region 0x%x-0x%x already in " + "use!\n", address + REGION_OFFSET, + address + REGION_OFFSET + REGION_LENGTH - 1); + err = -EBUSY; + goto exit; + } + + if (!(data = kmalloc(sizeof(struct w83627ehf_data), GFP_KERNEL))) { + err = -ENOMEM; + goto exit_release; + } + memset(data, 0, sizeof(struct w83627ehf_data)); + + client = &data->client; + client->addr = address; + client->data = data; + client->adapter = adapter; + client->driver = &w83627ehf_driver; + strcpy(client->name, "W83627EHF chip"); + init_MUTEX(&data->lock); + init_MUTEX(&data->update_lock); + + /* Tell the i2c layer a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto exit_free; + + w83627ehf_init_client(client); + + /* A few vars need to be filled upon startup */ + for (i = 0; i < 5; i++) + data->fan_min[i] = w83627ehf_read_value(client, + W83627EHF_REG_FAN_MIN[i]); + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + superio_enter(); + fan5pin = superio_inb(0x24) & 0x2; + fan4pin = superio_inb(0x29) & 0x6; + superio_exit(); + + /* It looks like fan4 and fan5 pins can be alternatively used + as fan on/off switches, but fan5 control is write only :/ + We assume that if the serial interface is disabled, designers + connected fan5 as input unless they are emitting log 1, which + is not the default. */ + + data->has_fan = 0x07; /* fan1, fan2 and fan3 */ + i = w83627ehf_read_value(client, W83627EHF_REG_FANDIV1); + if ((i & (1 << 2)) && (!fan4pin)) + data->has_fan |= (1 << 3); + if (!(i & (1 << 1)) && (!fan5pin)) + data->has_fan |= (1 << 4); + + /* Register a new directory entry with module sensors */ + if ((i = i2c_register_entry(client, + "w83627ehf", + w83627ehf_dir_table_template, + THIS_MODULE)) < 0) { + err = i; + goto exit_detach; + } + data->sysctl_id = i; + + return 0; + + exit_detach: + i2c_detach_client(client); + exit_free: + kfree(data); + exit_release: + release_region(address + REGION_OFFSET, REGION_LENGTH); + exit: + return err; + +} + +static int w83627ehf_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_detect(adapter, &addr_data, w83627ehf_detect); +} + +static int w83627ehf_detach_client(struct i2c_client *client) +{ + struct w83627ehf_data *data = client->data; + int err; + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "w83627ehf: Client deregistration failed\n"); + return err; + } + + release_region(client->addr + REGION_OFFSET, REGION_LENGTH); + kfree(client->data); + + return 0; +} + +static struct i2c_driver w83627ehf_driver = { + .name = "w83627ehf", + .flags = I2C_DF_NOTIFY, + .attach_adapter = w83627ehf_attach_adapter, + .detach_client = w83627ehf_detach_client, +}; + +static int __init w83627ehf_find(int sioaddr, unsigned int *addr) +{ + u16 val; + const char *devname; + + REG = sioaddr; + VAL = sioaddr + 1; + superio_enter(); + + val = (superio_inb(SIO_REG_DEVID) << 8) + | superio_inb(SIO_REG_DEVID + 1); + switch (val & SIO_ID_MASK) { + case SIO_W83627EHF_ID: + devname = "W83627EHF"; + break; + case SIO_W83627EHG_ID: + devname = "W83627EHG"; + break; + default: + superio_exit(); + return -ENODEV; + } + + superio_select(W83627EHF_LD_HWM); + val = (superio_inb(SIO_REG_ADDR) << 8) + | superio_inb(SIO_REG_ADDR + 1); + *addr = val & REGION_ALIGNMENT; + if (*addr == 0) { + printk(KERN_ERR "w83627ehf: HWM I/O area not set\n"); + superio_exit(); + return -ENODEV; + } + + printk(KERN_INFO "w83627ehf: Found %s device at 0x%x\n", + devname, *addr); + + /* Activate logical device if needed */ + val = superio_inb(SIO_REG_ENABLE); + if (!(val & 0x01)) { + printk(KERN_WARNING "w83627ehf: HWM disabled, enabling\n"); + superio_outb(SIO_REG_ENABLE, val | 0x01); + } + + superio_exit(); + return 0; +} + +static int __init sensors_w83627ehf_init(void) +{ + printk(KERN_INFO "w83627ehf: Driver version %s (%s)\n", + LM_VERSION, LM_DATE); + if (w83627ehf_find(0x2e, &normal_isa[0]) + && w83627ehf_find(0x4e, &normal_isa[0])) + return -ENODEV; + + return i2c_add_driver(&w83627ehf_driver); +} + +static void __exit sensors_w83627ehf_exit(void) +{ + i2c_del_driver(&w83627ehf_driver); +} + +MODULE_AUTHOR("Yuan Mu (Winbond), Jean Delvare "); +MODULE_DESCRIPTION("W83627EHF harware monitoring driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_w83627ehf_init); +module_exit(sensors_w83627ehf_exit); diff --git a/mkpatch/FILES b/mkpatch/FILES index 73fff819..1d06b242 100644 --- a/mkpatch/FILES +++ b/mkpatch/FILES @@ -65,6 +65,7 @@ kernel/chips/vt8231.c drivers/sensors/vt8231.c kernel/chips/w83781d.c drivers/sensors/w83781d.c kernel/chips/w83792d.c drivers/sensors/w83792d.c kernel/chips/w83627hf.c drivers/sensors/w83627hf.c +kernel/chips/w83627ehf.c drivers/sensors/w83627ehf.c kernel/chips/w83l785ts.c drivers/sensors/w83l785ts.c kernel/chips/xeontemp.c drivers/sensors/xeontemp.c kernel/include/sensors_compat.h include/linux/sensors_compat.h diff --git a/mkpatch/mkpatch.pl b/mkpatch/mkpatch.pl index 2ab14c71..504556f8 100755 --- a/mkpatch/mkpatch.pl +++ b/mkpatch/mkpatch.pl @@ -2,7 +2,7 @@ # mkpatch - Create patches against the Linux kernel # Copyright (C) 1999 Frodo Looijaard -# Copyright (C) 2005 Jean Delvare +# Copyright (C) 2005-2007 Jean Delvare # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -135,6 +135,7 @@ sub gen_Documentation_Configure_help m@Winbond W83781D, W83782D, W83783S@ or m@Winbond W83792D@ or m@Winbond W83627HF, W83627THF@ or + m@Winbond W83627EHF/EHG@ or m@Winbond W83L785TS-S@ or m@EEprom \(DIMM\) reader@) { $_ = ; @@ -730,6 +731,16 @@ CONFIG_SENSORS_W83627HF them in the lm_sensors package, which you can download at http://www.lm-sensors.org/ +Winbond W83627EHF/EHG +CONFIG_SENSORS_W83627EHF + If you say yes here you get support for the Winbond W83627EHF/EHG + sensor chips. This can also be built as a module which can be + inserted and removed while the kernel is running. + + You will also need the latest user-space utilities: you can find + them in the lm_sensors package, which you can download at + http://www.lm-sensors.org/ + Winbond W83L785TS-S CONFIG_SENSORS_W83L785TS If you say yes here you get support for the Winbond W83L785TS-S @@ -1063,6 +1074,7 @@ obj-$(CONFIG_SENSORS_VT8231) += vt8231.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83792D) += w83792d.o obj-$(CONFIG_SENSORS_W83627HF) += w83627hf.o +obj-$(CONFIG_SENSORS_W83627EHF) += w83627ehf.o obj-$(CONFIG_SENSORS_W83L785TS) += w83l785ts.o obj-$(CONFIG_SENSORS_XEONTEMP) += xeontemp.o