From 4efcb0a7368b0a5b060d048001e7ed9d7c63d081 Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 15 Jun 2007 09:15:18 +0000 Subject: [PATCH] New w83627ehf driver, for the W83627EHF/EHG chip. Backported from Linux 2.6, based on preliminary work by Yuan My (Winbond). Thanks to Bernardo Motta at Observit for providing the hardware that made it possible. Note that support for the W83627DHG isn't included, but it could easily be added later is anyone needs it. git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@4447 7894878c-1315-0410-8ee3-d5d059ff63e0 --- CHANGES | 2 + README | 3 +- doc/chips/SUMMARY | 3 + doc/chips/w83627ehf | 67 +++ kernel/chips/Module.mk | 3 + kernel/chips/w83627ehf.c | 1013 ++++++++++++++++++++++++++++++++++++++ mkpatch/FILES | 1 + mkpatch/mkpatch.pl | 14 +- 8 files changed, 1104 insertions(+), 2 deletions(-) create mode 100644 doc/chips/w83627ehf create mode 100644 kernel/chips/w83627ehf.c 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