diff options
Diffstat (limited to 'drivers/hwmon')
-rw-r--r-- | drivers/hwmon/Kconfig | 65 | ||||
-rw-r--r-- | drivers/hwmon/Makefile | 5 | ||||
-rw-r--r-- | drivers/hwmon/ams/ams.h | 2 | ||||
-rw-r--r-- | drivers/hwmon/asc7621.c | 9 | ||||
-rw-r--r-- | drivers/hwmon/coretemp.c | 23 | ||||
-rw-r--r-- | drivers/hwmon/emc1403.c | 33 | ||||
-rw-r--r-- | drivers/hwmon/emc2103.c | 740 | ||||
-rw-r--r-- | drivers/hwmon/f71882fg.c | 83 | ||||
-rw-r--r-- | drivers/hwmon/hdaps.c | 1 | ||||
-rw-r--r-- | drivers/hwmon/it87.c | 46 | ||||
-rw-r--r-- | drivers/hwmon/jc42.c | 593 | ||||
-rw-r--r-- | drivers/hwmon/jz4740-hwmon.c | 230 | ||||
-rw-r--r-- | drivers/hwmon/k8temp.c | 3 | ||||
-rw-r--r-- | drivers/hwmon/lm75.c | 39 | ||||
-rw-r--r-- | drivers/hwmon/lm75.h | 1 | ||||
-rw-r--r-- | drivers/hwmon/ltc4245.c | 177 | ||||
-rw-r--r-- | drivers/hwmon/mc13783-adc.c | 17 | ||||
-rw-r--r-- | drivers/hwmon/pc87360.c | 31 | ||||
-rw-r--r-- | drivers/hwmon/pc87427.c | 862 | ||||
-rw-r--r-- | drivers/hwmon/pkgtemp.c | 456 | ||||
-rw-r--r-- | drivers/hwmon/smm665.c | 743 | ||||
-rw-r--r-- | drivers/hwmon/ultra45_env.c | 8 | ||||
-rw-r--r-- | drivers/hwmon/via-cputemp.c | 2 | ||||
-rw-r--r-- | drivers/hwmon/w83627ehf.c | 97 |
24 files changed, 4108 insertions, 158 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index e19cf8eb6ccf..0fba82943125 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -332,11 +332,11 @@ config SENSORS_F71805F will be called f71805f. config SENSORS_F71882FG - tristate "Fintek F71858FG, F71862FG, F71882FG, F71889FG and F8000" + tristate "Fintek F71808E, F71858FG, F71862FG, F71882FG, F71889FG and F8000" depends on EXPERIMENTAL help - If you say yes here you get support for hardware monitoring - features of the Fintek F71858FG, F71862FG/71863FG, F71882FG/F71883FG, + If you say yes here you get support for hardware monitoring features + of the Fintek F71808E, F71858FG, F71862FG/71863FG, F71882FG/F71883FG, F71889FG and F8000 Super-I/O chips. This driver can also be built as a module. If so, the module @@ -405,7 +405,14 @@ config SENSORS_CORETEMP help If you say yes here you get support for the temperature sensor inside your CPU. Most of the family 6 CPUs - are supported. Check documentation/driver for details. + are supported. Check Documentation/hwmon/coretemp for details. + +config SENSORS_PKGTEMP + tristate "Intel processor package temperature sensor" + depends on X86 && PCI && EXPERIMENTAL + help + If you say yes here you get support for the package level temperature + sensor inside your CPU. Check documentation/driver for details. config SENSORS_IBMAEM tristate "IBM Active Energy Manager temperature/power sensors and control" @@ -446,6 +453,28 @@ config SENSORS_IT87 This driver can also be built as a module. If so, the module will be called it87. +config SENSORS_JZ4740 + tristate "Ingenic JZ4740 SoC ADC driver" + depends on MACH_JZ4740 && MFD_JZ4740_ADC + help + If you say yes here you get support for reading adc values from the ADCIN + pin on Ingenic JZ4740 SoC based boards. + + This driver can also be build as a module. If so, the module will be + called jz4740-hwmon. + +config SENSORS_JC42 + tristate "JEDEC JC42.4 compliant temperature sensors" + depends on I2C + help + If you say yes here you get support for Jedec JC42.4 compliant + temperature sensors. Support will include, but not be limited to, + ADT7408, CAT34TS02,, CAT6095, MAX6604, MCP9805, MCP98242, MCP98243, + MCP9843, SE97, SE98, STTS424, TSE2002B3, and TS3000B3. + + This driver can also be built as a module. If so, the module + will be called jc42. + config SENSORS_LM63 tristate "National Semiconductor LM63 and LM64" depends on I2C @@ -683,7 +712,8 @@ config SENSORS_PC87427 functions of the National Semiconductor PC87427 Super-I/O chip. The chip has two distinct logical devices, one for fan speed monitoring and control, and one for voltage and temperature - monitoring. Only fan speed monitoring is supported right now. + monitoring. Fan speed monitoring and control are supported, as + well as temperature monitoring. Voltages aren't supported yet. This driver can also be built as a module. If so, the module will be called pc87427. @@ -739,6 +769,21 @@ config SENSORS_SIS5595 This driver can also be built as a module. If so, the module will be called sis5595. +config SENSORS_SMM665 + tristate "Summit Microelectronics SMM665" + depends on I2C && EXPERIMENTAL + default n + help + If you say yes here you get support for the hardware monitoring + features of the Summit Microelectronics SMM665/SMM665B Six-Channel + Active DC Output Controller / Monitor. + + Other supported chips are SMM465, SMM665C, SMM764, and SMM766. + Support for those chips is untested. + + This driver can also be built as a module. If so, the module will + be called smm665. + config SENSORS_DME1737 tristate "SMSC DME1737, SCH311x and compatibles" depends on I2C && EXPERIMENTAL @@ -761,6 +806,16 @@ config SENSORS_EMC1403 Threshold values can be configured using sysfs. Data from the different diodes are accessible via sysfs. +config SENSORS_EMC2103 + tristate "SMSC EMC2103" + depends on I2C + help + If you say yes here you get support for the temperature + and fan sensors of the SMSC EMC2103 chips. + + This driver can also be built as a module. If so, the module + will be called emc2103. + config SENSORS_SMSC47M1 tristate "SMSC LPC47M10x and compatibles" help diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 2138ceb1a713..e3c2484f6c5f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -39,9 +39,11 @@ obj-$(CONFIG_SENSORS_AMS) += ams/ obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o +obj-$(CONFIG_SENSORS_PKGTEMP) += pkgtemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o obj-$(CONFIG_SENSORS_DS1621) += ds1621.o obj-$(CONFIG_SENSORS_EMC1403) += emc1403.o +obj-$(CONFIG_SENSORS_EMC2103) += emc2103.o obj-$(CONFIG_SENSORS_F71805F) += f71805f.o obj-$(CONFIG_SENSORS_F71882FG) += f71882fg.o obj-$(CONFIG_SENSORS_F75375S) += f75375s.o @@ -55,6 +57,8 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o obj-$(CONFIG_SENSORS_IT87) += it87.o +obj-$(CONFIG_SENSORS_JC42) += jc42.o +obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o @@ -86,6 +90,7 @@ obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_SENSORS_S3C) += s3c-hwmon.o obj-$(CONFIG_SENSORS_SHT15) += sht15.o obj-$(CONFIG_SENSORS_SIS5595) += sis5595.o +obj-$(CONFIG_SENSORS_SMM665) += smm665.o obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o diff --git a/drivers/hwmon/ams/ams.h b/drivers/hwmon/ams/ams.h index b28d7e27a031..90f094d45450 100644 --- a/drivers/hwmon/ams/ams.h +++ b/drivers/hwmon/ams/ams.h @@ -23,7 +23,7 @@ struct ams { /* General properties */ struct device_node *of_node; - struct of_device *of_dev; + struct platform_device *of_dev; char has_device; char vflag; u32 orient1; diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c index 3b973f30b1f6..89b4f3babe87 100644 --- a/drivers/hwmon/asc7621.c +++ b/drivers/hwmon/asc7621.c @@ -1150,9 +1150,6 @@ static int asc7621_detect(struct i2c_client *client, { struct i2c_adapter *adapter = client->adapter; int company, verstep, chip_index; - struct device *dev; - - dev = &client->dev; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; @@ -1169,13 +1166,11 @@ static int asc7621_detect(struct i2c_client *client, if (company == asc7621_chips[chip_index].company_id && verstep == asc7621_chips[chip_index].verstep_id) { - strlcpy(client->name, asc7621_chips[chip_index].name, - I2C_NAME_SIZE); strlcpy(info->type, asc7621_chips[chip_index].name, I2C_NAME_SIZE); - dev_info(&adapter->dev, "Matched %s\n", - asc7621_chips[chip_index].name); + dev_info(&adapter->dev, "Matched %s at 0x%02x\n", + asc7621_chips[chip_index].name, client->addr); return 0; } } diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 05344af50734..c070c9714cbe 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -480,7 +480,6 @@ exit: return err; } -#ifdef CONFIG_HOTPLUG_CPU static void coretemp_device_remove(unsigned int cpu) { struct pdev_entry *p, *n; @@ -515,7 +514,6 @@ static int __cpuinit coretemp_cpu_callback(struct notifier_block *nfb, static struct notifier_block coretemp_cpu_notifier __refdata = { .notifier_call = coretemp_cpu_callback, }; -#endif /* !CONFIG_HOTPLUG_CPU */ static int __init coretemp_init(void) { @@ -537,12 +535,9 @@ static int __init coretemp_init(void) * sensors. We check this bit only, all the early CPUs * without thermal sensors will be filtered out. */ - if (c->cpuid_level >= 6 && (cpuid_eax(0x06) & 0x01)) { - err = coretemp_device_add(i); - if (err) - goto exit_devices_unreg; - - } else { + if (c->cpuid_level >= 6 && (cpuid_eax(0x06) & 0x01)) + coretemp_device_add(i); + else { printk(KERN_INFO DRVNAME ": CPU (model=0x%x)" " has no thermal sensor.\n", c->x86_model); } @@ -552,21 +547,13 @@ static int __init coretemp_init(void) goto exit_driver_unreg; } -#ifdef CONFIG_HOTPLUG_CPU register_hotcpu_notifier(&coretemp_cpu_notifier); -#endif return 0; -exit_devices_unreg: - mutex_lock(&pdev_list_mutex); - list_for_each_entry_safe(p, n, &pdev_list, list) { - platform_device_unregister(p->pdev); - list_del(&p->list); - kfree(p); - } - mutex_unlock(&pdev_list_mutex); exit_driver_unreg: +#ifndef CONFIG_HOTPLUG_CPU platform_driver_unregister(&coretemp_driver); +#endif exit: return err; } diff --git a/drivers/hwmon/emc1403.c b/drivers/hwmon/emc1403.c index 0e4b5642638d..5b58b20dead1 100644 --- a/drivers/hwmon/emc1403.c +++ b/drivers/hwmon/emc1403.c @@ -89,6 +89,35 @@ static ssize_t store_temp(struct device *dev, return count; } +static ssize_t store_bit(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct thermal_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute_2 *sda = to_sensor_dev_attr_2(attr); + unsigned long val; + int retval; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + mutex_lock(&data->mutex); + retval = i2c_smbus_read_byte_data(client, sda->nr); + if (retval < 0) + goto fail; + + retval &= ~sda->index; + if (val) + retval |= sda->index; + + retval = i2c_smbus_write_byte_data(client, sda->index, retval); + if (retval == 0) + retval = count; +fail: + mutex_unlock(&data->mutex); + return retval; +} + static ssize_t show_hyst(struct device *dev, struct device_attribute *attr, char *buf) { @@ -200,6 +229,9 @@ static SENSOR_DEVICE_ATTR_2(temp3_crit_alarm, S_IRUGO, static SENSOR_DEVICE_ATTR(temp3_crit_hyst, S_IRUGO | S_IWUSR, show_hyst, store_hyst, 0x1A); +static SENSOR_DEVICE_ATTR_2(power_state, S_IRUGO | S_IWUSR, + show_bit, store_bit, 0x03, 0x40); + static struct attribute *mid_att_thermal[] = { &sensor_dev_attr_temp1_min.dev_attr.attr, &sensor_dev_attr_temp1_max.dev_attr.attr, @@ -225,6 +257,7 @@ static struct attribute *mid_att_thermal[] = { &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, &sensor_dev_attr_temp3_crit_hyst.dev_attr.attr, + &sensor_dev_attr_power_state.dev_attr.attr, NULL }; diff --git a/drivers/hwmon/emc2103.c b/drivers/hwmon/emc2103.c new file mode 100644 index 000000000000..af914ad93ece --- /dev/null +++ b/drivers/hwmon/emc2103.c @@ -0,0 +1,740 @@ +/* + emc2103.c - Support for SMSC EMC2103 + Copyright (c) 2010 SMSC + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> + +/* Addresses scanned */ +static const unsigned short normal_i2c[] = { 0x2E, I2C_CLIENT_END }; + +static const u8 REG_TEMP[4] = { 0x00, 0x02, 0x04, 0x06 }; +static const u8 REG_TEMP_MIN[4] = { 0x3c, 0x38, 0x39, 0x3a }; +static const u8 REG_TEMP_MAX[4] = { 0x34, 0x30, 0x31, 0x32 }; + +#define REG_CONF1 0x20 +#define REG_TEMP_MAX_ALARM 0x24 +#define REG_TEMP_MIN_ALARM 0x25 +#define REG_FAN_CONF1 0x42 +#define REG_FAN_TARGET_LO 0x4c +#define REG_FAN_TARGET_HI 0x4d +#define REG_FAN_TACH_HI 0x4e +#define REG_FAN_TACH_LO 0x4f +#define REG_PRODUCT_ID 0xfd +#define REG_MFG_ID 0xfe + +/* equation 4 from datasheet: rpm = (3932160 * multipler) / count */ +#define FAN_RPM_FACTOR 3932160 + +/* 2103-2 and 2103-4's 3rd temperature sensor can be connected to two diodes + * in anti-parallel mode, and in this configuration both can be read + * independently (so we have 4 temperature inputs). The device can't + * detect if it's connected in this mode, so we have to manually enable + * it. Default is to leave the device in the state it's already in (-1). + * This parameter allows APD mode to be optionally forced on or off */ +static int apd = -1; +module_param(apd, bool, 0); +MODULE_PARM_DESC(init, "Set to zero to disable anti-parallel diode mode"); + +struct temperature { + s8 degrees; + u8 fraction; /* 0-7 multiples of 0.125 */ +}; + +struct emc2103_data { + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; /* registers are valid */ + bool fan_rpm_control; + int temp_count; /* num of temp sensors */ + unsigned long last_updated; /* in jiffies */ + struct temperature temp[4]; /* internal + 3 external */ + s8 temp_min[4]; /* no fractional part */ + s8 temp_max[4]; /* no fractional part */ + u8 temp_min_alarm; + u8 temp_max_alarm; + u8 fan_multiplier; + u16 fan_tach; + u16 fan_target; +}; + +static int read_u8_from_i2c(struct i2c_client *client, u8 i2c_reg, u8 *output) +{ + int status = i2c_smbus_read_byte_data(client, i2c_reg); + if (status < 0) { + dev_warn(&client->dev, "reg 0x%02x, err %d\n", + i2c_reg, status); + } else { + *output = status; + } + return status; +} + +static void read_temp_from_i2c(struct i2c_client *client, u8 i2c_reg, + struct temperature *temp) +{ + u8 degrees, fractional; + + if (read_u8_from_i2c(client, i2c_reg, °rees) < 0) + return; + + if (read_u8_from_i2c(client, i2c_reg + 1, &fractional) < 0) + return; + + temp->degrees = degrees; + temp->fraction = (fractional & 0xe0) >> 5; +} + +static void read_fan_from_i2c(struct i2c_client *client, u16 *output, + u8 hi_addr, u8 lo_addr) +{ + u8 high_byte, lo_byte; + + if (read_u8_from_i2c(client, hi_addr, &high_byte) < 0) + return; + + if (read_u8_from_i2c(client, lo_addr, &lo_byte) < 0) + return; + + *output = ((u16)high_byte << 5) | (lo_byte >> 3); +} + +static void write_fan_target_to_i2c(struct i2c_client *client, u16 new_target) +{ + u8 high_byte = (new_target & 0x1fe0) >> 5; + u8 low_byte = (new_target & 0x001f) << 3; + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_LO, low_byte); + i2c_smbus_write_byte_data(client, REG_FAN_TARGET_HI, high_byte); +} + +static void read_fan_config_from_i2c(struct i2c_client *client) + +{ + struct emc2103_data *data = i2c_get_clientdata(client); + u8 conf1; + + if (read_u8_from_i2c(client, REG_FAN_CONF1, &conf1) < 0) + return; + + data->fan_multiplier = 1 << ((conf1 & 0x60) >> 5); + data->fan_rpm_control = (conf1 & 0x80) != 0; +} + +static struct emc2103_data *emc2103_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + int i; + + for (i = 0; i < data->temp_count; i++) { + read_temp_from_i2c(client, REG_TEMP[i], &data->temp[i]); + read_u8_from_i2c(client, REG_TEMP_MIN[i], + &data->temp_min[i]); + read_u8_from_i2c(client, REG_TEMP_MAX[i], + &data->temp_max[i]); + } + + read_u8_from_i2c(client, REG_TEMP_MIN_ALARM, + &data->temp_min_alarm); + read_u8_from_i2c(client, REG_TEMP_MAX_ALARM, + &data->temp_max_alarm); + + read_fan_from_i2c(client, &data->fan_tach, + REG_FAN_TACH_HI, REG_FAN_TACH_LO); + read_fan_from_i2c(client, &data->fan_target, + REG_FAN_TARGET_HI, REG_FAN_TARGET_LO); + read_fan_config_from_i2c(client); + + data->last_updated = jiffies; + data->valid = true; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +static ssize_t +show_temp(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp[nr].degrees * 1000 + + data->temp[nr].fraction * 125; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_min(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_min[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_max(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + int millidegrees = data->temp_max[nr] * 1000; + return sprintf(buf, "%d\n", millidegrees); +} + +static ssize_t +show_temp_fault(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = (data->temp[nr].degrees == -128); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +show_temp_min_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_min_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t +show_temp_max_alarm(struct device *dev, struct device_attribute *da, char *buf) +{ + int nr = to_sensor_dev_attr(da)->index; + struct emc2103_data *data = emc2103_update_device(dev); + bool alarm = data->temp_max_alarm & (1 << nr); + return sprintf(buf, "%d\n", alarm ? 1 : 0); +} + +static ssize_t set_temp_min(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long val; + + int result = strict_strtol(buf, 10, &val); + if (result < 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(val, 1000); + if ((val < -63) || (val > 127)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp_min[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MIN[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t set_temp_max(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int nr = to_sensor_dev_attr(da)->index; + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long val; + + int result = strict_strtol(buf, 10, &val); + if (result < 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(val, 1000); + if ((val < -63) || (val > 127)) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->temp_max[nr] = val; + i2c_smbus_write_byte_data(client, REG_TEMP_MAX[nr], val); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t +show_fan(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + if (data->fan_tach != 0) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) / data->fan_tach; + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t +show_fan_div(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int fan_div = 8 / data->fan_multiplier; + return sprintf(buf, "%d\n", fan_div); +} + +/* Note: we also update the fan target here, because its value is + determined in part by the fan clock divider. This follows the principle + of least surprise; the user doesn't expect the fan target to change just + because the divider changed. */ +static ssize_t set_fan_div(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = to_i2c_client(dev); + int new_range_bits, old_div = 8 / data->fan_multiplier; + long new_div; + + int status = strict_strtol(buf, 10, &new_div); + if (status < 0) + return -EINVAL; + + if (new_div == old_div) /* No change */ + return count; + + switch (new_div) { + case 1: + new_range_bits = 3; + break; + case 2: + new_range_bits = 2; + break; + case 4: + new_range_bits = 1; + break; + case 8: + new_range_bits = 0; + break; + default: + return -EINVAL; + } + + mutex_lock(&data->update_lock); + + status = i2c_smbus_read_byte_data(client, REG_FAN_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", + REG_FAN_CONF1, status); + mutex_unlock(&data->update_lock); + return -EIO; + } + status &= 0x9F; + status |= (new_range_bits << 5); + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, status); + + data->fan_multiplier = 8 / new_div; + + /* update fan target if high byte is not disabled */ + if ((data->fan_target & 0x1fe0) != 0x1fe0) { + u16 new_target = (data->fan_target * old_div) / new_div; + data->fan_target = min(new_target, (u16)0x1fff); + write_fan_target_to_i2c(client, data->fan_target); + } + + /* invalidate data to force re-read from hardware */ + data->valid = false; + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_fan_target(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + int rpm = 0; + + /* high byte of 0xff indicates disabled so return 0 */ + if ((data->fan_target != 0) && ((data->fan_target & 0x1fe0) != 0x1fe0)) + rpm = (FAN_RPM_FACTOR * data->fan_multiplier) + / data->fan_target; + + return sprintf(buf, "%d\n", rpm); +} + +static ssize_t set_fan_target(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct emc2103_data *data = emc2103_update_device(dev); + struct i2c_client *client = to_i2c_client(dev); + long rpm_target; + + int result = strict_strtol(buf, 10, &rpm_target); + if (result < 0) + return -EINVAL; + + /* Datasheet states 16384 as maximum RPM target (table 3.2) */ + if ((rpm_target < 0) || (rpm_target > 16384)) + return -EINVAL; + + mutex_lock(&data->update_lock); + + if (rpm_target == 0) + data->fan_target = 0x1fff; + else + data->fan_target = SENSORS_LIMIT( + (FAN_RPM_FACTOR * data->fan_multiplier) / rpm_target, + 0, 0x1fff); + + write_fan_target_to_i2c(client, data->fan_target); + + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t +show_fan_fault(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + bool fault = ((data->fan_tach & 0x1fe0) == 0x1fe0); + return sprintf(buf, "%d\n", fault ? 1 : 0); +} + +static ssize_t +show_pwm_enable(struct device *dev, struct device_attribute *da, char *buf) +{ + struct emc2103_data *data = emc2103_update_device(dev); + return sprintf(buf, "%d\n", data->fan_rpm_control ? 3 : 0); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct emc2103_data *data = i2c_get_clientdata(client); + long new_value; + u8 conf_reg; + + int result = strict_strtol(buf, 10, &new_value); + if (result < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + switch (new_value) { + case 0: + data->fan_rpm_control = false; + break; + case 3: + data->fan_rpm_control = true; + break; + default: + mutex_unlock(&data->update_lock); + return -EINVAL; + } + + read_u8_from_i2c(client, REG_FAN_CONF1, &conf_reg); + + if (data->fan_rpm_control) + conf_reg |= 0x80; + else + conf_reg &= ~0x80; + + i2c_smbus_write_byte_data(client, REG_FAN_CONF1, conf_reg); + + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 0); + +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 1); + +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 2); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 2); + +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO | S_IWUSR, show_temp_min, + set_temp_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO | S_IWUSR, show_temp_max, + set_temp_max, 3); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_temp_min_alarm, + NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_temp_max_alarm, + NULL, 3); + +static DEVICE_ATTR(fan1_input, S_IRUGO, show_fan, NULL); +static DEVICE_ATTR(fan1_div, S_IRUGO | S_IWUSR, show_fan_div, set_fan_div); +static DEVICE_ATTR(fan1_target, S_IRUGO | S_IWUSR, show_fan_target, + set_fan_target); +static DEVICE_ATTR(fan1_fault, S_IRUGO, show_fan_fault, NULL); + +static DEVICE_ATTR(pwm1_enable, S_IRUGO | S_IWUSR, show_pwm_enable, + set_pwm_enable); + +/* sensors present on all models */ +static struct attribute *emc2103_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &dev_attr_fan1_input.attr, + &dev_attr_fan1_div.attr, + &dev_attr_fan1_target.attr, + &dev_attr_fan1_fault.attr, + &dev_attr_pwm1_enable.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 */ +static struct attribute *emc2103_attributes_temp3[] = { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + NULL +}; + +/* extra temperature sensors only present on 2103-2 and 2103-4 in APD mode */ +static struct attribute *emc2103_attributes_temp4[] = { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group emc2103_group = { + .attrs = emc2103_attributes, +}; + +static const struct attribute_group emc2103_temp3_group = { + .attrs = emc2103_attributes_temp3, +}; + +static const struct attribute_group emc2103_temp4_group = { + .attrs = emc2103_attributes_temp4, +}; + +static int +emc2103_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct emc2103_data *data; + int status; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct emc2103_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* 2103-2 and 2103-4 have 3 external diodes, 2103-1 has 1 */ + status = i2c_smbus_read_byte_data(client, REG_PRODUCT_ID); + if (status == 0x24) { + /* 2103-1 only has 1 external diode */ + data->temp_count = 2; + } else { + /* 2103-2 and 2103-4 have 3 or 4 external diodes */ + status = i2c_smbus_read_byte_data(client, REG_CONF1); + if (status < 0) { + dev_dbg(&client->dev, "reg 0x%02x, err %d\n", REG_CONF1, + status); + goto exit_free; + } + + /* detect current state of hardware */ + data->temp_count = (status & 0x01) ? 4 : 3; + + /* force APD state if module parameter is set */ + if (apd == 0) { + /* force APD mode off */ + data->temp_count = 3; + status &= ~(0x01); + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } else if (apd == 1) { + /* force APD mode on */ + data->temp_count = 4; + status |= 0x01; + i2c_smbus_write_byte_data(client, REG_CONF1, status); + } + } + + /* Register sysfs hooks */ + status = sysfs_create_group(&client->dev.kobj, &emc2103_group); + if (status) + goto exit_free; + + if (data->temp_count >= 3) { + status = sysfs_create_group(&client->dev.kobj, + &emc2103_temp3_group); + if (status) + goto exit_remove; + } + + if (data->temp_count == 4) { + status = sysfs_create_group(&client->dev.kobj, + &emc2103_temp4_group); + if (status) + goto exit_remove_temp3; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + status = PTR_ERR(data->hwmon_dev); + goto exit_remove_temp4; + } + + dev_info(&client->dev, "%s: sensor '%s'\n", + dev_name(data->hwmon_dev), client->name); + + return 0; + +exit_remove_temp4: + if (data->temp_count == 4) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group); +exit_remove_temp3: + if (data->temp_count >= 3) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group); +exit_remove: + sysfs_remove_group(&client->dev.kobj, &emc2103_group); +exit_free: + kfree(data); + return status; +} + +static int emc2103_remove(struct i2c_client *client) +{ + struct emc2103_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + + if (data->temp_count == 4) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp4_group); + + if (data->temp_count >= 3) + sysfs_remove_group(&client->dev.kobj, &emc2103_temp3_group); + + sysfs_remove_group(&client->dev.kobj, &emc2103_group); + + kfree(data); + return 0; +} + +static const struct i2c_device_id emc2103_ids[] = { + { "emc2103", 0, }, + { /* LIST END */ } +}; +MODULE_DEVICE_TABLE(i2c, emc2103_ids); + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int +emc2103_detect(struct i2c_client *new_client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int manufacturer, product; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + manufacturer = i2c_smbus_read_byte_data(new_client, REG_MFG_ID); + if (manufacturer != 0x5D) + return -ENODEV; + + product = i2c_smbus_read_byte_data(new_client, REG_PRODUCT_ID); + if ((product != 0x24) && (product != 0x26)) + return -ENODEV; + + strlcpy(info->type, "emc2103", I2C_NAME_SIZE); + + return 0; +} + +static struct i2c_driver emc2103_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "emc2103", + }, + .probe = emc2103_probe, + .remove = emc2103_remove, + .id_table = emc2103_ids, + .detect = emc2103_detect, + .address_list = normal_i2c, +}; + +static int __init sensors_emc2103_init(void) +{ + return i2c_add_driver(&emc2103_driver); +} + +static void __exit sensors_emc2103_exit(void) +{ + i2c_del_driver(&emc2103_driver); +} + +MODULE_AUTHOR("Steve Glendinning <steve.glendinning@smsc.com>"); +MODULE_DESCRIPTION("SMSC EMC2103 hwmon driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_emc2103_init); +module_exit(sensors_emc2103_exit); diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 537841ef44b9..6207120dcd4d 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -45,6 +45,7 @@ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ +#define SIO_F71808_ID 0x0901 /* Chipset ID */ #define SIO_F71858_ID 0x0507 /* Chipset ID */ #define SIO_F71862_ID 0x0601 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */ @@ -96,9 +97,10 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -enum chips { f71858fg, f71862fg, f71882fg, f71889fg, f8000 }; +enum chips { f71808fg, f71858fg, f71862fg, f71882fg, f71889fg, f8000 }; static const char *f71882fg_names[] = { + "f71808fg", "f71858fg", "f71862fg", "f71882fg", @@ -306,8 +308,8 @@ static struct sensor_device_attribute_2 f71858fg_in_temp_attr[] = { SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), }; -/* Temp and in attr common to the f71862fg, f71882fg and f71889fg */ -static struct sensor_device_attribute_2 fxxxx_in_temp_attr[] = { +/* In attr common to the f71862fg, f71882fg and f71889fg */ +static struct sensor_device_attribute_2 fxxxx_in_attr[] = { SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), @@ -317,6 +319,22 @@ static struct sensor_device_attribute_2 fxxxx_in_temp_attr[] = { SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6), SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7), SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8), +}; + +/* In attr for the f71808fg */ +static struct sensor_device_attribute_2 f71808_in_attr[] = { + SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), + SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), + SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), + SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3), + SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4), + SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5), + SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 7), + SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 8), +}; + +/* Temp attr common to the f71808fg, f71862fg, f71882fg and f71889fg */ +static struct sensor_device_attribute_2 fxxxx_temp_attr[] = { SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 1), @@ -355,6 +373,10 @@ static struct sensor_device_attribute_2 fxxxx_in_temp_attr[] = { store_temp_beep, 0, 6), SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2), SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), +}; + +/* Temp and in attr common to the f71862fg, f71882fg and f71889fg */ +static struct sensor_device_attribute_2 f71862_temp_attr[] = { SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 3), SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, store_temp_max, 0, 3), @@ -989,6 +1011,11 @@ static struct f71882fg_data *f71882fg_update_device(struct device *dev) data->temp_type[1] = 6; break; } + } else if (data->type == f71808fg) { + reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + data->temp_type[1] = (reg & 0x02) ? 2 : 4; + data->temp_type[2] = (reg & 0x04) ? 2 : 4; + } else { reg2 = f71882fg_read8(data, F71882FG_REG_PECI); if ((reg2 & 0x03) == 0x01) @@ -1871,7 +1898,8 @@ static ssize_t store_pwm_auto_point_temp(struct device *dev, val /= 1000; - if (data->type == f71889fg) + if (data->type == f71889fg + || data->type == f71808fg) val = SENSORS_LIMIT(val, -128, 127); else val = SENSORS_LIMIT(val, 0, 127); @@ -1974,8 +2002,28 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) /* fall through! */ case f71862fg: err = f71882fg_create_sysfs_files(pdev, - fxxxx_in_temp_attr, - ARRAY_SIZE(fxxxx_in_temp_attr)); + f71862_temp_attr, + ARRAY_SIZE(f71862_temp_attr)); + if (err) + goto exit_unregister_sysfs; + err = f71882fg_create_sysfs_files(pdev, + fxxxx_in_attr, + ARRAY_SIZE(fxxxx_in_attr)); + if (err) + goto exit_unregister_sysfs; + err = f71882fg_create_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); + break; + case f71808fg: + err = f71882fg_create_sysfs_files(pdev, + f71808_in_attr, + ARRAY_SIZE(f71808_in_attr)); + if (err) + goto exit_unregister_sysfs; + err = f71882fg_create_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); break; case f8000: err = f71882fg_create_sysfs_files(pdev, @@ -2002,6 +2050,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) case f71862fg: err = (data->pwm_enable & 0x15) != 0x15; break; + case f71808fg: case f71882fg: case f71889fg: err = 0; @@ -2047,6 +2096,7 @@ static int __devinit f71882fg_probe(struct platform_device *pdev) f8000_auto_pwm_attr, ARRAY_SIZE(f8000_auto_pwm_attr)); break; + case f71808fg: case f71889fg: for (i = 0; i < nr_fans; i++) { data->pwm_auto_point_mapping[i] = @@ -2126,8 +2176,22 @@ static int f71882fg_remove(struct platform_device *pdev) /* fall through! */ case f71862fg: f71882fg_remove_sysfs_files(pdev, - fxxxx_in_temp_attr, - ARRAY_SIZE(fxxxx_in_temp_attr)); + f71862_temp_attr, + ARRAY_SIZE(f71862_temp_attr)); + f71882fg_remove_sysfs_files(pdev, + fxxxx_in_attr, + ARRAY_SIZE(fxxxx_in_attr)); + f71882fg_remove_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); + break; + case f71808fg: + f71882fg_remove_sysfs_files(pdev, + f71808_in_attr, + ARRAY_SIZE(f71808_in_attr)); + f71882fg_remove_sysfs_files(pdev, + fxxxx_temp_attr, + ARRAY_SIZE(fxxxx_temp_attr)); break; case f8000: f71882fg_remove_sysfs_files(pdev, @@ -2195,6 +2259,9 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address, devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); switch (devid) { + case SIO_F71808_ID: + sio_data->type = f71808fg; + break; case SIO_F71858_ID: sio_data->type = f71858fg; break; diff --git a/drivers/hwmon/hdaps.c b/drivers/hwmon/hdaps.c index be2d131e405c..bfd42f18924b 100644 --- a/drivers/hwmon/hdaps.c +++ b/drivers/hwmon/hdaps.c @@ -522,6 +522,7 @@ static struct dmi_system_id __initdata hdaps_whitelist[] = { HDAPS_DMI_MATCH_INVERT("IBM", "ThinkPad T42p", HDAPS_BOTH_AXES), HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T42"), HDAPS_DMI_MATCH_NORMAL("IBM", "ThinkPad T43"), + HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T400", HDAPS_BOTH_AXES), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T60", HDAPS_BOTH_AXES), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61p", HDAPS_BOTH_AXES), HDAPS_DMI_MATCH_INVERT("LENOVO", "ThinkPad T61", HDAPS_BOTH_AXES), diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index 25763d2223b6..f7701295937d 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -259,6 +259,7 @@ struct it87_sio_data { u8 revision; u8 vid_value; u8 beep_pin; + u8 internal; /* Internal sensors can be labeled */ /* Features skipped based on config or DMI */ u8 skip_vid; u8 skip_fan; @@ -1194,6 +1195,22 @@ static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr, } static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid_reg, NULL); +static ssize_t show_label(struct device *dev, struct device_attribute *attr, + char *buf) +{ + static const char *labels[] = { + "+5V", + "5VSB", + "Vbat", + }; + int nr = to_sensor_dev_attr(attr)->index; + + return sprintf(buf, "%s\n", labels[nr]); +} +static SENSOR_DEVICE_ATTR(in3_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(in7_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(in8_label, S_IRUGO, show_label, NULL, 2); + static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -1434,6 +1451,17 @@ static const struct attribute_group it87_group_vid = { .attrs = it87_attributes_vid, }; +static struct attribute *it87_attributes_label[] = { + &sensor_dev_attr_in3_label.dev_attr.attr, + &sensor_dev_attr_in7_label.dev_attr.attr, + &sensor_dev_attr_in8_label.dev_attr.attr, + NULL +}; + +static const struct attribute_group it87_group_label = { + .attrs = it87_attributes_vid, +}; + /* SuperIO detection - will change isa_address if a chip is found */ static int __init it87_find(unsigned short *address, struct it87_sio_data *sio_data) @@ -1487,6 +1515,9 @@ static int __init it87_find(unsigned short *address, pr_info("it87: Found IT%04xF chip at 0x%x, revision %d\n", chip_type, *address, sio_data->revision); + /* in8 (Vbat) is always internal */ + sio_data->internal = (1 << 2); + /* Read GPIO config and VID value from LDN 7 (GPIO) */ if (sio_data->type == it87) { /* The IT8705F doesn't have VID pins at all */ @@ -1540,9 +1571,9 @@ static int __init it87_find(unsigned short *address, pr_notice("it87: Routing internal VCCH to in7\n"); } if (reg & (1 << 0)) - pr_info("it87: in3 is VCC (+5V)\n"); + sio_data->internal |= (1 << 0); if (reg & (1 << 1)) - pr_info("it87: in7 is VCCH (+5V Stand-By)\n"); + sio_data->internal |= (1 << 1); sio_data->beep_pin = superio_inb(IT87_SIO_BEEP_PIN_REG) & 0x3f; } @@ -1600,6 +1631,7 @@ static void it87_remove_files(struct device *dev) } if (!sio_data->skip_vid) sysfs_remove_group(&dev->kobj, &it87_group_vid); + sysfs_remove_group(&dev->kobj, &it87_group_label); } static int __devinit it87_probe(struct platform_device *pdev) @@ -1725,6 +1757,16 @@ static int __devinit it87_probe(struct platform_device *pdev) goto ERROR4; } + /* Export labels for internal sensors */ + for (i = 0; i < 3; i++) { + if (!(sio_data->internal & (1 << i))) + continue; + err = sysfs_create_file(&dev->kobj, + it87_attributes_label[i]); + if (err) + goto ERROR4; + } + data->hwmon_dev = hwmon_device_register(dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c new file mode 100644 index 000000000000..340fc78c8dde --- /dev/null +++ b/drivers/hwmon/jc42.c @@ -0,0 +1,593 @@ +/* + * jc42.c - driver for Jedec JC42.4 compliant temperature sensors + * + * Copyright (c) 2010 Ericsson AB. + * + * Derived from lm77.c by Andras BALI <drewie@freemail.hu>. + * + * JC42.4 compliant temperature sensors are typically used on memory modules. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> + +/* Addresses to scan */ +static const unsigned short normal_i2c[] = { + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, I2C_CLIENT_END }; + +/* JC42 registers. All registers are 16 bit. */ +#define JC42_REG_CAP 0x00 +#define JC42_REG_CONFIG 0x01 +#define JC42_REG_TEMP_UPPER 0x02 +#define JC42_REG_TEMP_LOWER 0x03 +#define JC42_REG_TEMP_CRITICAL 0x04 +#define JC42_REG_TEMP 0x05 +#define JC42_REG_MANID 0x06 +#define JC42_REG_DEVICEID 0x07 + +/* Status bits in temperature register */ +#define JC42_ALARM_CRIT_BIT 15 +#define JC42_ALARM_MAX_BIT 14 +#define JC42_ALARM_MIN_BIT 13 + +/* Configuration register defines */ +#define JC42_CFG_CRIT_ONLY (1 << 2) +#define JC42_CFG_SHUTDOWN (1 << 8) +#define JC42_CFG_HYST_SHIFT 9 +#define JC42_CFG_HYST_MASK 0x03 + +/* Capabilities */ +#define JC42_CAP_RANGE (1 << 2) + +/* Manufacturer IDs */ +#define ADT_MANID 0x11d4 /* Analog Devices */ +#define MAX_MANID 0x004d /* Maxim */ +#define IDT_MANID 0x00b3 /* IDT */ +#define MCP_MANID 0x0054 /* Microchip */ +#define NXP_MANID 0x1131 /* NXP Semiconductors */ +#define ONS_MANID 0x1b09 /* ON Semiconductor */ +#define STM_MANID 0x104a /* ST Microelectronics */ + +/* Supported chips */ + +/* Analog Devices */ +#define ADT7408_DEVID 0x0801 +#define ADT7408_DEVID_MASK 0xffff + +/* IDT */ +#define TS3000B3_DEVID 0x2903 /* Also matches TSE2002B3 */ +#define TS3000B3_DEVID_MASK 0xffff + +/* Maxim */ +#define MAX6604_DEVID 0x3e00 +#define MAX6604_DEVID_MASK 0xffff + +/* Microchip */ +#define MCP98242_DEVID 0x2000 +#define MCP98242_DEVID_MASK 0xfffc + +#define MCP98243_DEVID 0x2100 +#define MCP98243_DEVID_MASK 0xfffc + +#define MCP9843_DEVID 0x0000 /* Also matches mcp9805 */ +#define MCP9843_DEVID_MASK 0xfffe + +/* NXP */ +#define SE97_DEVID 0xa200 +#define SE97_DEVID_MASK 0xfffc + +#define SE98_DEVID 0xa100 +#define SE98_DEVID_MASK 0xfffc + +/* ON Semiconductor */ +#define CAT6095_DEVID 0x0800 /* Also matches CAT34TS02 */ +#define CAT6095_DEVID_MASK 0xffe0 + +/* ST Microelectronics */ +#define STTS424_DEVID 0x0101 +#define STTS424_DEVID_MASK 0xffff + +#define STTS424E_DEVID 0x0000 +#define STTS424E_DEVID_MASK 0xfffe + +static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 }; + +struct jc42_chips { + u16 manid; + u16 devid; + u16 devid_mask; +}; + +static struct jc42_chips jc42_chips[] = { + { ADT_MANID, ADT7408_DEVID, ADT7408_DEVID_MASK }, + { IDT_MANID, TS3000B3_DEVID, TS3000B3_DEVID_MASK }, + { MAX_MANID, MAX6604_DEVID, MAX6604_DEVID_MASK }, + { MCP_MANID, MCP98242_DEVID, MCP98242_DEVID_MASK }, + { MCP_MANID, MCP98243_DEVID, MCP98243_DEVID_MASK }, + { MCP_MANID, MCP9843_DEVID, MCP9843_DEVID_MASK }, + { NXP_MANID, SE97_DEVID, SE97_DEVID_MASK }, + { ONS_MANID, CAT6095_DEVID, CAT6095_DEVID_MASK }, + { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, + { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, + { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, +}; + +/* Each client has this additional data */ +struct jc42_data { + struct device *hwmon_dev; + struct mutex update_lock; /* protect register access */ + bool extended; /* true if extended range supported */ + bool valid; + unsigned long last_updated; /* In jiffies */ + u16 orig_config; /* original configuration */ + u16 config; /* current configuration */ + u16 temp_input; /* Temperatures */ + u16 temp_crit; + u16 temp_min; + u16 temp_max; +}; + +static int jc42_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info); +static int jc42_remove(struct i2c_client *client); +static int jc42_read_value(struct i2c_client *client, u8 reg); +static int jc42_write_value(struct i2c_client *client, u8 reg, u16 value); + +static struct jc42_data *jc42_update_device(struct device *dev); + +static const struct i2c_device_id jc42_id[] = { + { "adt7408", 0 }, + { "cat94ts02", 0 }, + { "cat6095", 0 }, + { "jc42", 0 }, + { "max6604", 0 }, + { "mcp9805", 0 }, + { "mcp98242", 0 }, + { "mcp98243", 0 }, + { "mcp9843", 0 }, + { "se97", 0 }, + { "se97b", 0 }, + { "se98", 0 }, + { "stts424", 0 }, + { "tse2002b3", 0 }, + { "ts3000b3", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, jc42_id); + +#ifdef CONFIG_PM + +static int jc42_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct jc42_data *data = i2c_get_clientdata(client); + + data->config |= JC42_CFG_SHUTDOWN; + jc42_write_value(client, JC42_REG_CONFIG, data->config); + return 0; +} + +static int jc42_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct jc42_data *data = i2c_get_clientdata(client); + + data->config &= ~JC42_CFG_SHUTDOWN; + jc42_write_value(client, JC42_REG_CONFIG, data->config); + return 0; +} + +static const struct dev_pm_ops jc42_dev_pm_ops = { + .suspend = jc42_suspend, + .resume = jc42_resume, +}; + +#define JC42_DEV_PM_OPS (&jc42_dev_pm_ops) +#else +#define JC42_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + +/* This is the driver that will be inserted */ +static struct i2c_driver jc42_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "jc42", + .pm = JC42_DEV_PM_OPS, + }, + .probe = jc42_probe, + .remove = jc42_remove, + .id_table = jc42_id, + .detect = jc42_detect, + .address_list = normal_i2c, +}; + +#define JC42_TEMP_MIN_EXTENDED (-40000) +#define JC42_TEMP_MIN 0 +#define JC42_TEMP_MAX 125000 + +static u16 jc42_temp_to_reg(int temp, bool extended) +{ + int ntemp = SENSORS_LIMIT(temp, + extended ? JC42_TEMP_MIN_EXTENDED : + JC42_TEMP_MIN, JC42_TEMP_MAX); + + /* convert from 0.001 to 0.0625 resolution */ + return (ntemp * 2 / 125) & 0x1fff; +} + +static int jc42_temp_from_reg(s16 reg) +{ + reg &= 0x1fff; + + /* sign extend register */ + if (reg & 0x1000) + reg |= 0xf000; + + /* convert from 0.0625 to 0.001 resolution */ + return reg * 125 / 2; +} + +/* sysfs stuff */ + +/* read routines for temperature limits */ +#define show(value) \ +static ssize_t show_##value(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct jc42_data *data = jc42_update_device(dev); \ + if (IS_ERR(data)) \ + return PTR_ERR(data); \ + return sprintf(buf, "%d\n", jc42_temp_from_reg(data->value)); \ +} + +show(temp_input); +show(temp_crit); +show(temp_min); +show(temp_max); + +/* read routines for hysteresis values */ +static ssize_t show_temp_crit_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct jc42_data *data = jc42_update_device(dev); + int temp, hyst; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = jc42_temp_from_reg(data->temp_crit); + hyst = jc42_hysteresis[(data->config >> JC42_CFG_HYST_SHIFT) + & JC42_CFG_HYST_MASK]; + return sprintf(buf, "%d\n", temp - hyst); +} + +static ssize_t show_temp_max_hyst(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct jc42_data *data = jc42_update_device(dev); + int temp, hyst; + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = jc42_temp_from_reg(data->temp_max); + hyst = jc42_hysteresis[(data->config >> JC42_CFG_HYST_SHIFT) + & JC42_CFG_HYST_MASK]; + return sprintf(buf, "%d\n", temp - hyst); +} + +/* write routines */ +#define set(value, reg) \ +static ssize_t set_##value(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ +{ \ + struct i2c_client *client = to_i2c_client(dev); \ + struct jc42_data *data = i2c_get_clientdata(client); \ + int err, ret = count; \ + long val; \ + if (strict_strtol(buf, 10, &val) < 0) \ + return -EINVAL; \ + mutex_lock(&data->update_lock); \ + data->value = jc42_temp_to_reg(val, data->extended); \ + err = jc42_write_value(client, reg, data->value); \ + if (err < 0) \ + ret = err; \ + mutex_unlock(&data->update_lock); \ + return ret; \ +} + +set(temp_min, JC42_REG_TEMP_LOWER); +set(temp_max, JC42_REG_TEMP_UPPER); +set(temp_crit, JC42_REG_TEMP_CRITICAL); + +/* JC42.4 compliant chips only support four hysteresis values. + * Pick best choice and go from there. */ +static ssize_t set_temp_crit_hyst(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct jc42_data *data = i2c_get_clientdata(client); + long val; + int diff, hyst; + int err; + int ret = count; + + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + + diff = jc42_temp_from_reg(data->temp_crit) - val; + hyst = 0; + if (diff > 0) { + if (diff < 2250) + hyst = 1; /* 1.5 degrees C */ + else if (diff < 4500) + hyst = 2; /* 3.0 degrees C */ + else + hyst = 3; /* 6.0 degrees C */ + } + + mutex_lock(&data->update_lock); + data->config = (data->config + & ~(JC42_CFG_HYST_MASK << JC42_CFG_HYST_SHIFT)) + | (hyst << JC42_CFG_HYST_SHIFT); + err = jc42_write_value(client, JC42_REG_CONFIG, data->config); + if (err < 0) + ret = err; + mutex_unlock(&data->update_lock); + return ret; +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u16 bit = to_sensor_dev_attr(attr)->index; + struct jc42_data *data = jc42_update_device(dev); + u16 val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = data->temp_input; + if (bit != JC42_ALARM_CRIT_BIT && (data->config & JC42_CFG_CRIT_ONLY)) + val = 0; + return sprintf(buf, "%u\n", (val >> bit) & 1); +} + +static DEVICE_ATTR(temp1_input, S_IRUGO, + show_temp_input, NULL); +static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, + show_temp_crit, set_temp_crit); +static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, + show_temp_min, set_temp_min); +static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, + show_temp_max, set_temp_max); + +static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, + show_temp_crit_hyst, set_temp_crit_hyst); +static DEVICE_ATTR(temp1_max_hyst, S_IRUGO, + show_temp_max_hyst, NULL); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, + JC42_ALARM_CRIT_BIT); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_alarm, NULL, + JC42_ALARM_MIN_BIT); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, + JC42_ALARM_MAX_BIT); + +static struct attribute *jc42_attributes[] = { + &dev_attr_temp1_input.attr, + &dev_attr_temp1_crit.attr, + &dev_attr_temp1_min.attr, + &dev_attr_temp1_max.attr, + &dev_attr_temp1_crit_hyst.attr, + &dev_attr_temp1_max_hyst.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group jc42_group = { + .attrs = jc42_attributes, +}; + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int jc42_detect(struct i2c_client *new_client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = new_client->adapter; + int i, config, cap, manid, devid; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + cap = jc42_read_value(new_client, JC42_REG_CAP); + config = jc42_read_value(new_client, JC42_REG_CONFIG); + manid = jc42_read_value(new_client, JC42_REG_MANID); + devid = jc42_read_value(new_client, JC42_REG_DEVICEID); + + if (cap < 0 || config < 0 || manid < 0 || devid < 0) + return -ENODEV; + + if ((cap & 0xff00) || (config & 0xf800)) + return -ENODEV; + + for (i = 0; i < ARRAY_SIZE(jc42_chips); i++) { + struct jc42_chips *chip = &jc42_chips[i]; + if (manid == chip->manid && + (devid & chip->devid_mask) == chip->devid) { + strlcpy(info->type, "jc42", I2C_NAME_SIZE); + return 0; + } + } + return -ENODEV; +} + +static int jc42_probe(struct i2c_client *new_client, + const struct i2c_device_id *id) +{ + struct jc42_data *data; + int config, cap, err; + + data = kzalloc(sizeof(struct jc42_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(new_client, data); + mutex_init(&data->update_lock); + + cap = jc42_read_value(new_client, JC42_REG_CAP); + if (cap < 0) { + err = -EINVAL; + goto exit_free; + } + data->extended = !!(cap & JC42_CAP_RANGE); + + config = jc42_read_value(new_client, JC42_REG_CONFIG); + if (config < 0) { + err = -EINVAL; + goto exit_free; + } + data->orig_config = config; + if (config & JC42_CFG_SHUTDOWN) { + config &= ~JC42_CFG_SHUTDOWN; + jc42_write_value(new_client, JC42_REG_CONFIG, config); + } + data->config = config; + + /* Register sysfs hooks */ + err = sysfs_create_group(&new_client->dev.kobj, &jc42_group); + if (err) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&new_client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + sysfs_remove_group(&new_client->dev.kobj, &jc42_group); +exit_free: + kfree(data); +exit: + return err; +} + +static int jc42_remove(struct i2c_client *client) +{ + struct jc42_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &jc42_group); + if (data->config != data->orig_config) + jc42_write_value(client, JC42_REG_CONFIG, data->orig_config); + kfree(data); + return 0; +} + +/* All registers are word-sized. */ +static int jc42_read_value(struct i2c_client *client, u8 reg) +{ + int ret = i2c_smbus_read_word_data(client, reg); + if (ret < 0) + return ret; + return swab16(ret); +} + +static int jc42_write_value(struct i2c_client *client, u8 reg, u16 value) +{ + return i2c_smbus_write_word_data(client, reg, swab16(value)); +} + +static struct jc42_data *jc42_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct jc42_data *data = i2c_get_clientdata(client); + struct jc42_data *ret = data; + int val; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + val = jc42_read_value(client, JC42_REG_TEMP); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_input = val; + + val = jc42_read_value(client, JC42_REG_TEMP_CRITICAL); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_crit = val; + + val = jc42_read_value(client, JC42_REG_TEMP_LOWER); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_min = val; + + val = jc42_read_value(client, JC42_REG_TEMP_UPPER); + if (val < 0) { + ret = ERR_PTR(val); + goto abort; + } + data->temp_max = val; + + data->last_updated = jiffies; + data->valid = true; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static int __init sensors_jc42_init(void) +{ + return i2c_add_driver(&jc42_driver); +} + +static void __exit sensors_jc42_exit(void) +{ + i2c_del_driver(&jc42_driver); +} + +MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>"); +MODULE_DESCRIPTION("JC42 driver"); +MODULE_LICENSE("GPL"); + +module_init(sensors_jc42_init); +module_exit(sensors_jc42_exit); diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c new file mode 100644 index 000000000000..1c8b3d9e2051 --- /dev/null +++ b/drivers/hwmon/jz4740-hwmon.c @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SoC HWMON driver + * + * 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. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/completion.h> +#include <linux/mfd/core.h> + +#include <linux/hwmon.h> + +struct jz4740_hwmon { + struct resource *mem; + void __iomem *base; + + int irq; + + struct mfd_cell *cell; + struct device *hwmon; + + struct completion read_completion; + + struct mutex lock; +}; + +static ssize_t jz4740_hwmon_show_name(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + return sprintf(buf, "jz4740\n"); +} + +static irqreturn_t jz4740_hwmon_irq(int irq, void *data) +{ + struct jz4740_hwmon *hwmon = data; + + complete(&hwmon->read_completion); + return IRQ_HANDLED; +} + +static ssize_t jz4740_hwmon_read_adcin(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct jz4740_hwmon *hwmon = dev_get_drvdata(dev); + struct completion *completion = &hwmon->read_completion; + unsigned long t; + unsigned long val; + int ret; + + mutex_lock(&hwmon->lock); + + INIT_COMPLETION(*completion); + + enable_irq(hwmon->irq); + hwmon->cell->enable(to_platform_device(dev)); + + t = wait_for_completion_interruptible_timeout(completion, HZ); + + if (t > 0) { + val = readw(hwmon->base) & 0xfff; + val = (val * 3300) >> 12; + ret = sprintf(buf, "%lu\n", val); + } else { + ret = t ? t : -ETIMEDOUT; + } + + hwmon->cell->disable(to_platform_device(dev)); + disable_irq(hwmon->irq); + + mutex_unlock(&hwmon->lock); + + return ret; +} + +static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL); +static DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL); + +static struct attribute *jz4740_hwmon_attributes[] = { + &dev_attr_name.attr, + &dev_attr_in0_input.attr, + NULL +}; + +static const struct attribute_group jz4740_hwmon_attr_group = { + .attrs = jz4740_hwmon_attributes, +}; + +static int __devinit jz4740_hwmon_probe(struct platform_device *pdev) +{ + int ret; + struct jz4740_hwmon *hwmon; + + hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) { + dev_err(&pdev->dev, "Failed to allocate driver structure\n"); + return -ENOMEM; + } + + hwmon->cell = pdev->dev.platform_data; + + hwmon->irq = platform_get_irq(pdev, 0); + if (hwmon->irq < 0) { + ret = hwmon->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free; + } + + hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!hwmon->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); + goto err_free; + } + + hwmon->mem = request_mem_region(hwmon->mem->start, + resource_size(hwmon->mem), pdev->name); + if (!hwmon->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request mmio memory region\n"); + goto err_free; + } + + hwmon->base = ioremap_nocache(hwmon->mem->start, + resource_size(hwmon->mem)); + if (!hwmon->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); + goto err_release_mem_region; + } + + init_completion(&hwmon->read_completion); + mutex_init(&hwmon->lock); + + platform_set_drvdata(pdev, hwmon); + + ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_iounmap; + } + disable_irq(hwmon->irq); + + ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); + if (ret) { + dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret); + goto err_free_irq; + } + + hwmon->hwmon = hwmon_device_register(&pdev->dev); + if (IS_ERR(hwmon->hwmon)) { + ret = PTR_ERR(hwmon->hwmon); + goto err_remove_file; + } + + return 0; + +err_remove_file: + sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); +err_free_irq: + free_irq(hwmon->irq, hwmon); +err_iounmap: + platform_set_drvdata(pdev, NULL); + iounmap(hwmon->base); +err_release_mem_region: + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem)); +err_free: + kfree(hwmon); + + return ret; +} + +static int __devexit jz4740_hwmon_remove(struct platform_device *pdev) +{ + struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev); + + hwmon_device_unregister(hwmon->hwmon); + sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group); + + free_irq(hwmon->irq, hwmon); + + iounmap(hwmon->base); + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem)); + + platform_set_drvdata(pdev, NULL); + kfree(hwmon); + + return 0; +} + +struct platform_driver jz4740_hwmon_driver = { + .probe = jz4740_hwmon_probe, + .remove = __devexit_p(jz4740_hwmon_remove), + .driver = { + .name = "jz4740-hwmon", + .owner = THIS_MODULE, + }, +}; + +static int __init jz4740_hwmon_init(void) +{ + return platform_driver_register(&jz4740_hwmon_driver); +} +module_init(jz4740_hwmon_init); + +static void __exit jz4740_hwmon_exit(void) +{ + platform_driver_unregister(&jz4740_hwmon_driver); +} +module_exit(jz4740_hwmon_exit); + +MODULE_DESCRIPTION("JZ4740 SoC HWMON driver"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:jz4740-hwmon"); diff --git a/drivers/hwmon/k8temp.c b/drivers/hwmon/k8temp.c index 8bdf80d91598..b9bb3e0ca530 100644 --- a/drivers/hwmon/k8temp.c +++ b/drivers/hwmon/k8temp.c @@ -252,12 +252,13 @@ static int __devinit k8temp_probe(struct pci_dev *pdev, &sensor_dev_attr_temp3_input.dev_attr); if (err) goto exit_remove; - if (data->sensorsp & SEL_PLACE) + if (data->sensorsp & SEL_PLACE) { err = device_create_file(&pdev->dev, &sensor_dev_attr_temp4_input. dev_attr); if (err) goto exit_remove; + } } err = device_create_file(&pdev->dev, &dev_attr_name); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 393f354f92a4..ab5b87a81677 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -280,10 +280,49 @@ static int lm75_detect(struct i2c_client *new_client, return 0; } +#ifdef CONFIG_PM +static int lm75_suspend(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = lm75_read_value(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status | LM75_SHUTDOWN; + lm75_write_value(client, LM75_REG_CONF, status); + return 0; +} + +static int lm75_resume(struct device *dev) +{ + int status; + struct i2c_client *client = to_i2c_client(dev); + status = lm75_read_value(client, LM75_REG_CONF); + if (status < 0) { + dev_dbg(&client->dev, "Can't read config? %d\n", status); + return status; + } + status = status & ~LM75_SHUTDOWN; + lm75_write_value(client, LM75_REG_CONF, status); + return 0; +} + +static const struct dev_pm_ops lm75_dev_pm_ops = { + .suspend = lm75_suspend, + .resume = lm75_resume, +}; +#define LM75_DEV_PM_OPS (&lm75_dev_pm_ops) +#else +#define LM75_DEV_PM_OPS NULL +#endif /* CONFIG_PM */ + static struct i2c_driver lm75_driver = { .class = I2C_CLASS_HWMON, .driver = { .name = "lm75", + .pm = LM75_DEV_PM_OPS, }, .probe = lm75_probe, .remove = lm75_remove, diff --git a/drivers/hwmon/lm75.h b/drivers/hwmon/lm75.h index 7c93454bb4e3..e547a3eb4de3 100644 --- a/drivers/hwmon/lm75.h +++ b/drivers/hwmon/lm75.h @@ -30,6 +30,7 @@ /* straight from the datasheet */ #define LM75_TEMP_MIN (-55000) #define LM75_TEMP_MAX 125000 +#define LM75_SHUTDOWN 0x01 /* TEMP: 0.001C/bit (-55C to +125C) REG: (0.5C/bit, two's complement) << 7 */ diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c index 21d201befc2c..659308329308 100644 --- a/drivers/hwmon/ltc4245.c +++ b/drivers/hwmon/ltc4245.c @@ -21,6 +21,7 @@ #include <linux/i2c.h> #include <linux/hwmon.h> #include <linux/hwmon-sysfs.h> +#include <linux/i2c/ltc4245.h> /* Here are names of the chip's registers (a.k.a. commands) */ enum ltc4245_cmd { @@ -60,8 +61,72 @@ struct ltc4245_data { /* Voltage registers */ u8 vregs[0x0d]; + + /* GPIO ADC registers */ + bool use_extra_gpios; + int gpios[3]; }; +/* + * Update the readings from the GPIO pins. If the driver has been configured to + * sample all GPIO's as analog voltages, a round-robin sampling method is used. + * Otherwise, only the configured GPIO pin is sampled. + * + * LOCKING: must hold data->update_lock + */ +static void ltc4245_update_gpios(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4245_data *data = i2c_get_clientdata(client); + u8 gpio_curr, gpio_next, gpio_reg; + int i; + + /* no extra gpio support, we're basically done */ + if (!data->use_extra_gpios) { + data->gpios[0] = data->vregs[LTC4245_GPIOADC - 0x10]; + return; + } + + /* + * If the last reading was too long ago, then we mark all old GPIO + * readings as stale by setting them to -EAGAIN + */ + if (time_after(jiffies, data->last_updated + 5 * HZ)) { + dev_dbg(&client->dev, "Marking GPIOs invalid\n"); + for (i = 0; i < ARRAY_SIZE(data->gpios); i++) + data->gpios[i] = -EAGAIN; + } + + /* + * Get the current GPIO pin + * + * The datasheet calls these GPIO[1-3], but we'll calculate the zero + * based array index instead, and call them GPIO[0-2]. This is much + * easier to think about. + */ + gpio_curr = (data->cregs[LTC4245_GPIO] & 0xc0) >> 6; + if (gpio_curr > 0) + gpio_curr -= 1; + + /* Read the GPIO voltage from the GPIOADC register */ + data->gpios[gpio_curr] = data->vregs[LTC4245_GPIOADC - 0x10]; + + /* Find the next GPIO pin to read */ + gpio_next = (gpio_curr + 1) % ARRAY_SIZE(data->gpios); + + /* + * Calculate the correct setting for the GPIO register so it will + * sample the next GPIO pin + */ + gpio_reg = (data->cregs[LTC4245_GPIO] & 0x3f) | ((gpio_next + 1) << 6); + + /* Update the GPIO register */ + i2c_smbus_write_byte_data(client, LTC4245_GPIO, gpio_reg); + + /* Update saved data */ + data->cregs[LTC4245_GPIO] = gpio_reg; +} + static struct ltc4245_data *ltc4245_update_device(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); @@ -93,6 +158,9 @@ static struct ltc4245_data *ltc4245_update_device(struct device *dev) data->vregs[i] = val; } + /* Update GPIO readings */ + ltc4245_update_gpios(dev); + data->last_updated = jiffies; data->valid = 1; } @@ -233,6 +301,22 @@ static ssize_t ltc4245_show_alarm(struct device *dev, return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); } +static ssize_t ltc4245_show_gpio(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct ltc4245_data *data = ltc4245_update_device(dev); + int val = data->gpios[attr->index]; + + /* handle stale GPIO's */ + if (val < 0) + return val; + + /* Convert to millivolts and print */ + return snprintf(buf, PAGE_SIZE, "%u\n", val * 10); +} + /* These macros are used below in constructing device attribute objects * for use with sysfs_create_group() to make a sysfs device file * for each register. @@ -254,6 +338,10 @@ static ssize_t ltc4245_show_alarm(struct device *dev, static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ ltc4245_show_alarm, NULL, (mask), reg) +#define LTC4245_GPIO_VOLTAGE(name, gpio_num) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_gpio, NULL, gpio_num) + /* Construct a sensor_device_attribute structure for each register */ /* Input voltages */ @@ -293,7 +381,9 @@ LTC4245_ALARM(in7_min_alarm, (1 << 2), LTC4245_FAULT2); LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2); /* GPIO voltages */ -LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC); +LTC4245_GPIO_VOLTAGE(in9_input, 0); +LTC4245_GPIO_VOLTAGE(in10_input, 1); +LTC4245_GPIO_VOLTAGE(in11_input, 2); /* Power Consumption (virtual) */ LTC4245_POWER(power1_input, LTC4245_12VSENSE); @@ -304,7 +394,7 @@ LTC4245_POWER(power4_input, LTC4245_VEESENSE); /* Finally, construct an array of pointers to members of the above objects, * as required for sysfs_create_group() */ -static struct attribute *ltc4245_attributes[] = { +static struct attribute *ltc4245_std_attributes[] = { &sensor_dev_attr_in1_input.dev_attr.attr, &sensor_dev_attr_in2_input.dev_attr.attr, &sensor_dev_attr_in3_input.dev_attr.attr, @@ -345,10 +435,77 @@ static struct attribute *ltc4245_attributes[] = { NULL, }; -static const struct attribute_group ltc4245_group = { - .attrs = ltc4245_attributes, +static struct attribute *ltc4245_gpio_attributes[] = { + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + NULL, +}; + +static const struct attribute_group ltc4245_std_group = { + .attrs = ltc4245_std_attributes, +}; + +static const struct attribute_group ltc4245_gpio_group = { + .attrs = ltc4245_gpio_attributes, }; +static int ltc4245_sysfs_create_groups(struct i2c_client *client) +{ + struct ltc4245_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + int ret; + + /* register the standard sysfs attributes */ + ret = sysfs_create_group(&dev->kobj, <c4245_std_group); + if (ret) { + dev_err(dev, "unable to register standard attributes\n"); + return ret; + } + + /* if we're using the extra gpio support, register it's attributes */ + if (data->use_extra_gpios) { + ret = sysfs_create_group(&dev->kobj, <c4245_gpio_group); + if (ret) { + dev_err(dev, "unable to register gpio attributes\n"); + sysfs_remove_group(&dev->kobj, <c4245_std_group); + return ret; + } + } + + return 0; +} + +static void ltc4245_sysfs_remove_groups(struct i2c_client *client) +{ + struct ltc4245_data *data = i2c_get_clientdata(client); + struct device *dev = &client->dev; + + if (data->use_extra_gpios) + sysfs_remove_group(&dev->kobj, <c4245_gpio_group); + + sysfs_remove_group(&dev->kobj, <c4245_std_group); +} + +static bool ltc4245_use_extra_gpios(struct i2c_client *client) +{ + struct ltc4245_platform_data *pdata = dev_get_platdata(&client->dev); +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; +#endif + + /* prefer platform data */ + if (pdata) + return pdata->use_extra_gpios; + +#ifdef CONFIG_OF + /* fallback on OF */ + if (of_find_property(np, "ltc4245,use-extra-gpios", NULL)) + return true; +#endif + + return false; +} + static int ltc4245_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -367,15 +524,16 @@ static int ltc4245_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); + data->use_extra_gpios = ltc4245_use_extra_gpios(client); /* Initialize the LTC4245 chip */ i2c_smbus_write_byte_data(client, LTC4245_FAULT1, 0x00); i2c_smbus_write_byte_data(client, LTC4245_FAULT2, 0x00); /* Register sysfs hooks */ - ret = sysfs_create_group(&client->dev.kobj, <c4245_group); + ret = ltc4245_sysfs_create_groups(client); if (ret) - goto out_sysfs_create_group; + goto out_sysfs_create_groups; data->hwmon_dev = hwmon_device_register(&client->dev); if (IS_ERR(data->hwmon_dev)) { @@ -386,8 +544,8 @@ static int ltc4245_probe(struct i2c_client *client, return 0; out_hwmon_device_register: - sysfs_remove_group(&client->dev.kobj, <c4245_group); -out_sysfs_create_group: + ltc4245_sysfs_remove_groups(client); +out_sysfs_create_groups: kfree(data); out_kzalloc: return ret; @@ -398,8 +556,7 @@ static int ltc4245_remove(struct i2c_client *client) struct ltc4245_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, <c4245_group); - + ltc4245_sysfs_remove_groups(client); kfree(data); return 0; diff --git a/drivers/hwmon/mc13783-adc.c b/drivers/hwmon/mc13783-adc.c index ce3c7bc81814..d5226c9e1201 100644 --- a/drivers/hwmon/mc13783-adc.c +++ b/drivers/hwmon/mc13783-adc.c @@ -18,7 +18,7 @@ * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <linux/mfd/mc13783-private.h> +#include <linux/mfd/mc13783.h> #include <linux/platform_device.h> #include <linux/hwmon-sysfs.h> #include <linux/kernel.h> @@ -144,6 +144,14 @@ static const struct attribute_group mc13783_group_ts = { .attrs = mc13783_attr_ts, }; +static int mc13783_adc_use_touchscreen(struct platform_device *pdev) +{ + struct mc13783_adc_priv *priv = platform_get_drvdata(pdev); + unsigned flags = mc13783_get_flags(priv->mc13783); + + return flags & MC13783_USE_TOUCHSCREEN; +} + static int __init mc13783_adc_probe(struct platform_device *pdev) { struct mc13783_adc_priv *priv; @@ -162,10 +170,11 @@ static int __init mc13783_adc_probe(struct platform_device *pdev) if (ret) goto out_err_create1; - if (!(priv->mc13783->flags & MC13783_USE_TOUCHSCREEN)) + if (!mc13783_adc_use_touchscreen(pdev)) { ret = sysfs_create_group(&pdev->dev.kobj, &mc13783_group_ts); if (ret) goto out_err_create2; + } priv->hwmon_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(priv->hwmon_dev)) { @@ -180,7 +189,7 @@ static int __init mc13783_adc_probe(struct platform_device *pdev) out_err_register: - if (!(priv->mc13783->flags & MC13783_USE_TOUCHSCREEN)) + if (!mc13783_adc_use_touchscreen(pdev)) sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); out_err_create2: @@ -199,7 +208,7 @@ static int __devexit mc13783_adc_remove(struct platform_device *pdev) hwmon_device_unregister(priv->hwmon_dev); - if (!(priv->mc13783->flags & MC13783_USE_TOUCHSCREEN)) + if (!mc13783_adc_use_touchscreen(pdev)) sysfs_remove_group(&pdev->dev.kobj, &mc13783_group_ts); sysfs_remove_group(&pdev->dev.kobj, &mc13783_group); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 4a64b85d4ec9..68e69a49633c 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -1610,11 +1610,8 @@ static struct pc87360_data *pc87360_update_device(struct device *dev) static int __init pc87360_device_add(unsigned short address) { - struct resource res = { - .name = "pc87360", - .flags = IORESOURCE_IO, - }; - int err, i; + struct resource res[3]; + int err, i, res_count; pdev = platform_device_alloc("pc87360", address); if (!pdev) { @@ -1623,22 +1620,28 @@ static int __init pc87360_device_add(unsigned short address) goto exit; } + memset(res, 0, 3 * sizeof(struct resource)); + res_count = 0; for (i = 0; i < 3; i++) { if (!extra_isa[i]) continue; - res.start = extra_isa[i]; - res.end = extra_isa[i] + PC87360_EXTENT - 1; + res[res_count].start = extra_isa[i]; + res[res_count].end = extra_isa[i] + PC87360_EXTENT - 1; + res[res_count].name = "pc87360", + res[res_count].flags = IORESOURCE_IO, - err = acpi_check_resource_conflict(&res); + err = acpi_check_resource_conflict(&res[res_count]); if (err) goto exit_device_put; - err = platform_device_add_resources(pdev, &res, 1); - if (err) { - printk(KERN_ERR "pc87360: Device resource[%d] " - "addition failed (%d)\n", i, err); - goto exit_device_put; - } + res_count++; + } + + err = platform_device_add_resources(pdev, res, res_count); + if (err) { + printk(KERN_ERR "pc87360: Device resources addition failed " + "(%d)\n", err); + goto exit_device_put; } err = platform_device_add(pdev); diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c index 3170b26d2443..9ec4daaf6ca6 100644 --- a/drivers/hwmon/pc87427.c +++ b/drivers/hwmon/pc87427.c @@ -1,7 +1,7 @@ /* * pc87427.c - hardware monitoring driver for the * National Semiconductor PC87427 Super-I/O chip - * Copyright (C) 2006 Jean Delvare <khali@linux-fr.org> + * Copyright (C) 2006, 2008, 2010 Jean Delvare <khali@linux-fr.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -15,10 +15,11 @@ * Supports the following chips: * * Chip #vin #fan #pwm #temp devid - * PC87427 - 8 - - 0xF2 + * PC87427 - 8 4 6 0xF2 * * This driver assumes that no more than one chip is present. - * Only fan inputs are supported so far, although the chip can do much more. + * Only fans are fully supported so far. Temperatures are in read-only + * mode, and voltages aren't supported at all. */ #include <linux/module.h> @@ -57,6 +58,25 @@ struct pc87427_data { u16 fan[8]; /* register values */ u16 fan_min[8]; /* register values */ u8 fan_status[8]; /* register values */ + + u8 pwm_enabled; /* bit vector */ + u8 pwm_auto_ok; /* bit vector */ + u8 pwm_enable[4]; /* register values */ + u8 pwm[4]; /* register values */ + + u8 temp_enabled; /* bit vector */ + s16 temp[6]; /* register values */ + s8 temp_min[6]; /* register values */ + s8 temp_max[6]; /* register values */ + s8 temp_crit[6]; /* register values */ + u8 temp_status[6]; /* register values */ + u8 temp_type[6]; /* register values */ +}; + +struct pc87427_sio_data { + unsigned short address[2]; + u8 has_fanin; + u8 has_fanout; }; /* @@ -65,6 +85,13 @@ struct pc87427_data { #define SIOREG_LDSEL 0x07 /* Logical device select */ #define SIOREG_DEVID 0x20 /* Device ID */ +#define SIOREG_CF2 0x22 /* Configuration 2 */ +#define SIOREG_CF3 0x23 /* Configuration 3 */ +#define SIOREG_CF4 0x24 /* Configuration 4 */ +#define SIOREG_CF5 0x25 /* Configuration 5 */ +#define SIOREG_CFB 0x2B /* Configuration B */ +#define SIOREG_CFC 0x2C /* Configuration C */ +#define SIOREG_CFD 0x2D /* Configuration D */ #define SIOREG_ACT 0x30 /* Device activation */ #define SIOREG_MAP 0x50 /* I/O or memory mapping */ #define SIOREG_IOBASE 0x60 /* I/O base address */ @@ -102,6 +129,8 @@ static inline void superio_exit(int sioaddr) #define BANK_FM(nr) (nr) #define BANK_FT(nr) (0x08 + (nr)) #define BANK_FC(nr) (0x10 + (nr) * 2) +#define BANK_TM(nr) (nr) +#define BANK_VM(nr) (0x08 + (nr)) /* * I/O access functions @@ -179,6 +208,127 @@ static inline u16 fan_to_reg(unsigned long val) } /* + * PWM registers and conversions + */ + +#define PC87427_REG_PWM_ENABLE 0x10 +#define PC87427_REG_PWM_DUTY 0x12 + +#define PWM_ENABLE_MODE_MASK (7 << 4) +#define PWM_ENABLE_CTLEN (1 << 0) + +#define PWM_MODE_MANUAL (0 << 4) +#define PWM_MODE_AUTO (1 << 4) +#define PWM_MODE_OFF (2 << 4) +#define PWM_MODE_ON (7 << 4) + +/* Dedicated function to read all registers related to a given PWM output. + This saves us quite a few locks and bank selections. + Must be called with data->lock held. + nr is from 0 to 3 */ +static void pc87427_readall_pwm(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_FAN]; + + outb(BANK_FC(nr), iobase + PC87427_REG_BANK); + data->pwm_enable[nr] = inb(iobase + PC87427_REG_PWM_ENABLE); + data->pwm[nr] = inb(iobase + PC87427_REG_PWM_DUTY); +} + +static inline int pwm_enable_from_reg(u8 reg) +{ + switch (reg & PWM_ENABLE_MODE_MASK) { + case PWM_MODE_ON: + return 0; + case PWM_MODE_MANUAL: + case PWM_MODE_OFF: + return 1; + case PWM_MODE_AUTO: + return 2; + default: + return -EPROTO; + } +} + +static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval) +{ + switch (val) { + default: + return PWM_MODE_ON; + case 1: + return pwmval ? PWM_MODE_MANUAL : PWM_MODE_OFF; + case 2: + return PWM_MODE_AUTO; + } +} + +/* + * Temperature registers and conversions + */ + +#define PC87427_REG_TEMP_STATUS 0x10 +#define PC87427_REG_TEMP 0x14 +#define PC87427_REG_TEMP_MAX 0x18 +#define PC87427_REG_TEMP_MIN 0x19 +#define PC87427_REG_TEMP_CRIT 0x1a +#define PC87427_REG_TEMP_TYPE 0x1d + +#define TEMP_STATUS_CHANEN (1 << 0) +#define TEMP_STATUS_LOWFLG (1 << 1) +#define TEMP_STATUS_HIGHFLG (1 << 2) +#define TEMP_STATUS_CRITFLG (1 << 3) +#define TEMP_STATUS_SENSERR (1 << 5) +#define TEMP_TYPE_MASK (3 << 5) + +#define TEMP_TYPE_THERMISTOR (1 << 5) +#define TEMP_TYPE_REMOTE_DIODE (2 << 5) +#define TEMP_TYPE_LOCAL_DIODE (3 << 5) + +/* Dedicated function to read all registers related to a given temperature + input. This saves us quite a few locks and bank selections. + Must be called with data->lock held. + nr is from 0 to 5 */ +static void pc87427_readall_temp(struct pc87427_data *data, u8 nr) +{ + int iobase = data->address[LD_TEMP]; + + outb(BANK_TM(nr), iobase + PC87427_REG_BANK); + data->temp[nr] = le16_to_cpu(inw(iobase + PC87427_REG_TEMP)); + data->temp_max[nr] = inb(iobase + PC87427_REG_TEMP_MAX); + data->temp_min[nr] = inb(iobase + PC87427_REG_TEMP_MIN); + data->temp_crit[nr] = inb(iobase + PC87427_REG_TEMP_CRIT); + data->temp_type[nr] = inb(iobase + PC87427_REG_TEMP_TYPE); + data->temp_status[nr] = inb(iobase + PC87427_REG_TEMP_STATUS); + /* Clear fan alarm bits */ + outb(data->temp_status[nr], iobase + PC87427_REG_TEMP_STATUS); +} + +static inline unsigned int temp_type_from_reg(u8 reg) +{ + switch (reg & TEMP_TYPE_MASK) { + case TEMP_TYPE_THERMISTOR: + return 4; + case TEMP_TYPE_REMOTE_DIODE: + case TEMP_TYPE_LOCAL_DIODE: + return 3; + default: + return 0; + } +} + +/* We assume 8-bit thermal sensors; 9-bit thermal sensors are possible + too, but I have no idea how to figure out when they are used. */ +static inline long temp_from_reg(s16 reg) +{ + return reg * 1000 / 256; +} + +static inline long temp_from_reg8(s8 reg) +{ + return reg * 1000; +} + +/* * Data interface */ @@ -198,6 +348,21 @@ static struct pc87427_data *pc87427_update_device(struct device *dev) continue; pc87427_readall_fan(data, i); } + + /* PWM outputs */ + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + pc87427_readall_pwm(data, i); + } + + /* Temperature channels */ + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + pc87427_readall_temp(data, i); + } + data->last_updated = jiffies; done: @@ -208,9 +373,8 @@ done: static ssize_t show_fan_input(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct pc87427_data *data = pc87427_update_device(dev); - int nr = attr->index; + int nr = to_sensor_dev_attr(devattr)->index; return sprintf(buf, "%lu\n", fan_from_reg(data->fan[nr])); } @@ -218,9 +382,8 @@ static ssize_t show_fan_input(struct device *dev, struct device_attribute static ssize_t show_fan_min(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct pc87427_data *data = pc87427_update_device(dev); - int nr = attr->index; + int nr = to_sensor_dev_attr(devattr)->index; return sprintf(buf, "%lu\n", fan_from_reg(data->fan_min[nr])); } @@ -228,9 +391,8 @@ static ssize_t show_fan_min(struct device *dev, struct device_attribute static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct pc87427_data *data = pc87427_update_device(dev); - int nr = attr->index; + int nr = to_sensor_dev_attr(devattr)->index; return sprintf(buf, "%d\n", !!(data->fan_status[nr] & FAN_STATUS_LOSPD)); @@ -239,9 +401,8 @@ static ssize_t show_fan_alarm(struct device *dev, struct device_attribute static ssize_t show_fan_fault(struct device *dev, struct device_attribute *devattr, char *buf) { - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); struct pc87427_data *data = pc87427_update_device(dev); - int nr = attr->index; + int nr = to_sensor_dev_attr(devattr)->index; return sprintf(buf, "%d\n", !!(data->fan_status[nr] & FAN_STATUS_STALL)); @@ -251,11 +412,13 @@ static ssize_t set_fan_min(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct pc87427_data *data = dev_get_drvdata(dev); - struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); - int nr = attr->index; - unsigned long val = simple_strtoul(buf, NULL, 10); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; int iobase = data->address[LD_FAN]; + if (strict_strtoul(buf, 10, &val) < 0) + return -EINVAL; + mutex_lock(&data->lock); outb(BANK_FM(nr), iobase + PC87427_REG_BANK); /* The low speed limit registers are read-only while monitoring @@ -377,6 +540,390 @@ static const struct attribute_group pc87427_group_fan[8] = { { .attrs = pc87427_attributes_fan[7] }, }; +/* Must be called with data->lock held and pc87427_readall_pwm() freshly + called */ +static void update_pwm_enable(struct pc87427_data *data, int nr, u8 mode) +{ + int iobase = data->address[LD_FAN]; + data->pwm_enable[nr] &= ~PWM_ENABLE_MODE_MASK; + data->pwm_enable[nr] |= mode; + outb(data->pwm_enable[nr], iobase + PC87427_REG_PWM_ENABLE); +} + +static ssize_t show_pwm_enable(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + int pwm_enable; + + pwm_enable = pwm_enable_from_reg(data->pwm_enable[nr]); + if (pwm_enable < 0) + return pwm_enable; + return sprintf(buf, "%d\n", pwm_enable); +} + +static ssize_t set_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; + + if (strict_strtoul(buf, 10, &val) < 0 || val > 2) + return -EINVAL; + /* Can't go to automatic mode if it isn't configured */ + if (val == 2 && !(data->pwm_auto_ok & (1 << nr))) + return -EINVAL; + + mutex_lock(&data->lock); + pc87427_readall_pwm(data, nr); + update_pwm_enable(data, nr, pwm_enable_to_reg(val, data->pwm[nr])); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t show_pwm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", (int)data->pwm[nr]); +} + +static ssize_t set_pwm(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr(devattr)->index; + unsigned long val; + int iobase = data->address[LD_FAN]; + u8 mode; + + if (strict_strtoul(buf, 10, &val) < 0 || val > 0xff) + return -EINVAL; + + mutex_lock(&data->lock); + pc87427_readall_pwm(data, nr); + mode = data->pwm_enable[nr] & PWM_ENABLE_MODE_MASK; + if (mode != PWM_MODE_MANUAL && mode != PWM_MODE_OFF) { + dev_notice(dev, "Can't set PWM%d duty cycle while not in " + "manual mode\n", nr + 1); + mutex_unlock(&data->lock); + return -EPERM; + } + + /* We may have to change the mode */ + if (mode == PWM_MODE_MANUAL && val == 0) { + /* Transition from Manual to Off */ + update_pwm_enable(data, nr, PWM_MODE_OFF); + mode = PWM_MODE_OFF; + dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, + "manual", "off"); + } else if (mode == PWM_MODE_OFF && val != 0) { + /* Transition from Off to Manual */ + update_pwm_enable(data, nr, PWM_MODE_MANUAL); + mode = PWM_MODE_MANUAL; + dev_dbg(dev, "Switching PWM%d from %s to %s\n", nr + 1, + "off", "manual"); + } + + data->pwm[nr] = val; + if (mode == PWM_MODE_MANUAL) + outb(val, iobase + PC87427_REG_PWM_DUTY); + mutex_unlock(&data->lock); + + return count; +} + +static SENSOR_DEVICE_ATTR(pwm1_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 0); +static SENSOR_DEVICE_ATTR(pwm2_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 1); +static SENSOR_DEVICE_ATTR(pwm3_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 2); +static SENSOR_DEVICE_ATTR(pwm4_enable, S_IWUSR | S_IRUGO, + show_pwm_enable, set_pwm_enable, 3); + +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(pwm3, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 2); +static SENSOR_DEVICE_ATTR(pwm4, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 3); + +static struct attribute *pc87427_attributes_pwm[4][3] = { + { + &sensor_dev_attr_pwm1_enable.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm2_enable.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm3_enable.dev_attr.attr, + &sensor_dev_attr_pwm3.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_pwm4_enable.dev_attr.attr, + &sensor_dev_attr_pwm4.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_pwm[4] = { + { .attrs = pc87427_attributes_pwm[0] }, + { .attrs = pc87427_attributes_pwm[1] }, + { .attrs = pc87427_attributes_pwm[2] }, + { .attrs = pc87427_attributes_pwm[3] }, +}; + +static ssize_t show_temp_input(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg(data->temp[nr])); +} + +static ssize_t show_temp_min(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_min[nr])); +} + +static ssize_t show_temp_max(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_max[nr])); +} + +static ssize_t show_temp_crit(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%ld\n", temp_from_reg8(data->temp_crit[nr])); +} + +static ssize_t show_temp_type(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%u\n", temp_type_from_reg(data->temp_type[nr])); +} + +static ssize_t show_temp_min_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_LOWFLG)); +} + +static ssize_t show_temp_max_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_HIGHFLG)); +} + +static ssize_t show_temp_crit_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_CRITFLG)); +} + +static ssize_t show_temp_fault(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pc87427_data *data = pc87427_update_device(dev); + int nr = to_sensor_dev_attr(devattr)->index; + + return sprintf(buf, "%d\n", !!(data->temp_status[nr] + & TEMP_STATUS_SENSERR)); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_temp_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_temp_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_input, S_IRUGO, show_temp_input, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_input, S_IRUGO, show_temp_input, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min, S_IRUGO, show_temp_min, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_min, S_IRUGO, show_temp_min, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_min, S_IRUGO, show_temp_min, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_min, S_IRUGO, show_temp_min, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_min, S_IRUGO, show_temp_min, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IRUGO, show_temp_max, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max, S_IRUGO, show_temp_max, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max, S_IRUGO, show_temp_max, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max, S_IRUGO, show_temp_max, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max, S_IRUGO, show_temp_max, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IRUGO, show_temp_crit, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit, S_IRUGO, show_temp_crit, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit, S_IRUGO, show_temp_crit, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit, S_IRUGO, show_temp_crit, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit, S_IRUGO, show_temp_crit, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_type, S_IRUGO, show_temp_type, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_type, S_IRUGO, show_temp_type, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_type, S_IRUGO, show_temp_type, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_min_alarm, S_IRUGO, + show_temp_min_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_max_alarm, S_IRUGO, + show_temp_max_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_crit_alarm, S_IRUGO, + show_temp_crit_alarm, NULL, 5); + +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2); +static SENSOR_DEVICE_ATTR(temp4_fault, S_IRUGO, show_temp_fault, NULL, 3); +static SENSOR_DEVICE_ATTR(temp5_fault, S_IRUGO, show_temp_fault, NULL, 4); +static SENSOR_DEVICE_ATTR(temp6_fault, S_IRUGO, show_temp_fault, NULL, 5); + +static struct attribute *pc87427_attributes_temp[6][10] = { + { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_type.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp2_type.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_crit.dev_attr.attr, + &sensor_dev_attr_temp3_type.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_crit.dev_attr.attr, + &sensor_dev_attr_temp4_type.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp5_input.dev_attr.attr, + &sensor_dev_attr_temp5_min.dev_attr.attr, + &sensor_dev_attr_temp5_max.dev_attr.attr, + &sensor_dev_attr_temp5_crit.dev_attr.attr, + &sensor_dev_attr_temp5_type.dev_attr.attr, + &sensor_dev_attr_temp5_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp5_fault.dev_attr.attr, + NULL + }, { + &sensor_dev_attr_temp6_input.dev_attr.attr, + &sensor_dev_attr_temp6_min.dev_attr.attr, + &sensor_dev_attr_temp6_max.dev_attr.attr, + &sensor_dev_attr_temp6_crit.dev_attr.attr, + &sensor_dev_attr_temp6_type.dev_attr.attr, + &sensor_dev_attr_temp6_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp6_fault.dev_attr.attr, + NULL + } +}; + +static const struct attribute_group pc87427_group_temp[6] = { + { .attrs = pc87427_attributes_temp[0] }, + { .attrs = pc87427_attributes_temp[1] }, + { .attrs = pc87427_attributes_temp[2] }, + { .attrs = pc87427_attributes_temp[3] }, + { .attrs = pc87427_attributes_temp[4] }, + { .attrs = pc87427_attributes_temp[5] }, +}; + static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) { @@ -391,8 +938,49 @@ static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); * Device detection, attach and detach */ +static void pc87427_release_regions(struct platform_device *pdev, int count) +{ + struct resource *res; + int i; + + for (i = 0; i < count; i++) { + res = platform_get_resource(pdev, IORESOURCE_IO, i); + release_region(res->start, resource_size(res)); + } +} + +static int __devinit pc87427_request_regions(struct platform_device *pdev, + int count) +{ + struct resource *res; + int i, err = 0; + + for (i = 0; i < count; i++) { + res = platform_get_resource(pdev, IORESOURCE_IO, i); + if (!res) { + err = -ENOENT; + dev_err(&pdev->dev, "Missing resource #%d\n", i); + break; + } + if (!request_region(res->start, resource_size(res), DRVNAME)) { + err = -EBUSY; + dev_err(&pdev->dev, + "Failed to request region 0x%lx-0x%lx\n", + (unsigned long)res->start, + (unsigned long)res->end); + break; + } + } + + if (err && i) + pc87427_release_regions(pdev, i); + + return err; +} + static void __devinit pc87427_init_device(struct device *dev) { + struct pc87427_sio_data *sio_data = dev->platform_data; struct pc87427_data *data = dev_get_drvdata(dev); int i; u8 reg; @@ -400,10 +988,12 @@ static void __devinit pc87427_init_device(struct device *dev) /* The FMC module should be ready */ reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK); if (!(reg & 0x80)) - dev_warn(dev, "FMC module not ready!\n"); + dev_warn(dev, "%s module not ready!\n", "FMC"); /* Check which fans are enabled */ for (i = 0; i < 8; i++) { + if (!(sio_data->has_fanin & (1 << i))) /* Not wired */ + continue; reg = pc87427_read8_bank(data, LD_FAN, BANK_FM(i), PC87427_REG_FAN_STATUS); if (reg & FAN_STATUS_MONEN) @@ -411,37 +1001,93 @@ static void __devinit pc87427_init_device(struct device *dev) } if (!data->fan_enabled) { - dev_dbg(dev, "Enabling all fan inputs\n"); - for (i = 0; i < 8; i++) + dev_dbg(dev, "Enabling monitoring of all fans\n"); + for (i = 0; i < 8; i++) { + if (!(sio_data->has_fanin & (1 << i))) /* Not wired */ + continue; pc87427_write8_bank(data, LD_FAN, BANK_FM(i), PC87427_REG_FAN_STATUS, FAN_STATUS_MONEN); - data->fan_enabled = 0xff; + } + data->fan_enabled = sio_data->has_fanin; + } + + /* Check which PWM outputs are enabled */ + for (i = 0; i < 4; i++) { + if (!(sio_data->has_fanout & (1 << i))) /* Not wired */ + continue; + reg = pc87427_read8_bank(data, LD_FAN, BANK_FC(i), + PC87427_REG_PWM_ENABLE); + if (reg & PWM_ENABLE_CTLEN) + data->pwm_enabled |= (1 << i); + + /* We don't expose an interface to reconfigure the automatic + fan control mode, so only allow to return to this mode if + it was originally set. */ + if ((reg & PWM_ENABLE_MODE_MASK) == PWM_MODE_AUTO) { + dev_dbg(dev, "PWM%d is in automatic control mode\n", + i + 1); + data->pwm_auto_ok |= (1 << i); + } + } + + /* The HMC module should be ready */ + reg = pc87427_read8(data, LD_TEMP, PC87427_REG_BANK); + if (!(reg & 0x80)) + dev_warn(dev, "%s module not ready!\n", "HMC"); + + /* Check which temperature channels are enabled */ + for (i = 0; i < 6; i++) { + reg = pc87427_read8_bank(data, LD_TEMP, BANK_TM(i), + PC87427_REG_TEMP_STATUS); + if (reg & TEMP_STATUS_CHANEN) + data->temp_enabled |= (1 << i); + } +} + +static void pc87427_remove_files(struct device *dev) +{ + struct pc87427_data *data = dev_get_drvdata(dev); + int i; + + device_remove_file(dev, &dev_attr_name); + for (i = 0; i < 8; i++) { + if (!(data->fan_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_fan[i]); + } + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_pwm[i]); + } + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + sysfs_remove_group(&dev->kobj, &pc87427_group_temp[i]); } } static int __devinit pc87427_probe(struct platform_device *pdev) { + struct pc87427_sio_data *sio_data = pdev->dev.platform_data; struct pc87427_data *data; - struct resource *res; - int i, err; + int i, err, res_count; - if (!(data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL))) { + data = kzalloc(sizeof(struct pc87427_data), GFP_KERNEL); + if (!data) { err = -ENOMEM; printk(KERN_ERR DRVNAME ": Out of memory\n"); goto exit; } - /* This will need to be revisited when we add support for - temperature and voltage monitoring. */ - res = platform_get_resource(pdev, IORESOURCE_IO, 0); - if (!request_region(res->start, resource_size(res), DRVNAME)) { - err = -EBUSY; - dev_err(&pdev->dev, "Failed to request region 0x%lx-0x%lx\n", - (unsigned long)res->start, (unsigned long)res->end); + data->address[0] = sio_data->address[0]; + data->address[1] = sio_data->address[1]; + res_count = (data->address[0] != 0) + (data->address[1] != 0); + + err = pc87427_request_regions(pdev, res_count); + if (err) goto exit_kfree; - } - data->address[0] = res->start; mutex_init(&data->lock); data->name = "pc87427"; @@ -449,13 +1095,31 @@ static int __devinit pc87427_probe(struct platform_device *pdev) pc87427_init_device(&pdev->dev); /* Register sysfs hooks */ - if ((err = device_create_file(&pdev->dev, &dev_attr_name))) + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) goto exit_release_region; for (i = 0; i < 8; i++) { if (!(data->fan_enabled & (1 << i))) continue; - if ((err = sysfs_create_group(&pdev->dev.kobj, - &pc87427_group_fan[i]))) + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_fan[i]); + if (err) + goto exit_remove_files; + } + for (i = 0; i < 4; i++) { + if (!(data->pwm_enabled & (1 << i))) + continue; + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_pwm[i]); + if (err) + goto exit_remove_files; + } + for (i = 0; i < 6; i++) { + if (!(data->temp_enabled & (1 << i))) + continue; + err = sysfs_create_group(&pdev->dev.kobj, + &pc87427_group_temp[i]); + if (err) goto exit_remove_files; } @@ -469,13 +1133,9 @@ static int __devinit pc87427_probe(struct platform_device *pdev) return 0; exit_remove_files: - for (i = 0; i < 8; i++) { - if (!(data->fan_enabled & (1 << i))) - continue; - sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); - } + pc87427_remove_files(&pdev->dev); exit_release_region: - release_region(res->start, resource_size(res)); + pc87427_release_regions(pdev, res_count); exit_kfree: platform_set_drvdata(pdev, NULL); kfree(data); @@ -486,21 +1146,16 @@ exit: static int __devexit pc87427_remove(struct platform_device *pdev) { struct pc87427_data *data = platform_get_drvdata(pdev); - struct resource *res; - int i; + int res_count; + + res_count = (data->address[0] != 0) + (data->address[1] != 0); hwmon_device_unregister(data->hwmon_dev); - device_remove_file(&pdev->dev, &dev_attr_name); - for (i = 0; i < 8; i++) { - if (!(data->fan_enabled & (1 << i))) - continue; - sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_fan[i]); - } + pc87427_remove_files(&pdev->dev); platform_set_drvdata(pdev, NULL); kfree(data); - res = platform_get_resource(pdev, IORESOURCE_IO, 0); - release_region(res->start, resource_size(res)); + pc87427_release_regions(pdev, res_count); return 0; } @@ -515,34 +1170,50 @@ static struct platform_driver pc87427_driver = { .remove = __devexit_p(pc87427_remove), }; -static int __init pc87427_device_add(unsigned short address) +static int __init pc87427_device_add(const struct pc87427_sio_data *sio_data) { - struct resource res = { - .start = address, - .end = address + REGION_LENGTH - 1, - .name = logdev_str[0], - .flags = IORESOURCE_IO, + struct resource res[2] = { + { .flags = IORESOURCE_IO }, + { .flags = IORESOURCE_IO }, }; - int err; + int err, i, res_count; - err = acpi_check_resource_conflict(&res); - if (err) - goto exit; + res_count = 0; + for (i = 0; i < 2; i++) { + if (!sio_data->address[i]) + continue; + res[res_count].start = sio_data->address[i]; + res[res_count].end = sio_data->address[i] + REGION_LENGTH - 1; + res[res_count].name = logdev_str[i]; - pdev = platform_device_alloc(DRVNAME, address); + err = acpi_check_resource_conflict(&res[res_count]); + if (err) + goto exit; + + res_count++; + } + + pdev = platform_device_alloc(DRVNAME, res[0].start); if (!pdev) { err = -ENOMEM; printk(KERN_ERR DRVNAME ": Device allocation failed\n"); goto exit; } - err = platform_device_add_resources(pdev, &res, 1); + err = platform_device_add_resources(pdev, res, res_count); if (err) { printk(KERN_ERR DRVNAME ": Device resource addition failed " "(%d)\n", err); goto exit_device_put; } + err = platform_device_add_data(pdev, sio_data, + sizeof(struct pc87427_sio_data)); + if (err) { + printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); + goto exit_device_put; + } + err = platform_device_add(pdev); if (err) { printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", @@ -558,9 +1229,10 @@ exit: return err; } -static int __init pc87427_find(int sioaddr, unsigned short *address) +static int __init pc87427_find(int sioaddr, struct pc87427_sio_data *sio_data) { u16 val; + u8 cfg, cfg_b; int i, err = 0; /* Identify device */ @@ -571,7 +1243,7 @@ static int __init pc87427_find(int sioaddr, unsigned short *address) } for (i = 0; i < 2; i++) { - address[i] = 0; + sio_data->address[i] = 0; /* Select logical device */ superio_outb(sioaddr, SIOREG_LDSEL, logdev[i]); @@ -596,9 +1268,58 @@ static int __init pc87427_find(int sioaddr, unsigned short *address) "for logical device 0x%02x\n", logdev[i]); continue; } - address[i] = val; + sio_data->address[i] = val; } + /* No point in loading the driver if everything is disabled */ + if (!sio_data->address[0] && !sio_data->address[1]) { + err = -ENODEV; + goto exit; + } + + /* Check which fan inputs are wired */ + sio_data->has_fanin = (1 << 2) | (1 << 3); /* FANIN2, FANIN3 */ + + cfg = superio_inb(sioaddr, SIOREG_CF2); + if (!(cfg & (1 << 3))) + sio_data->has_fanin |= (1 << 0); /* FANIN0 */ + if (!(cfg & (1 << 2))) + sio_data->has_fanin |= (1 << 4); /* FANIN4 */ + + cfg = superio_inb(sioaddr, SIOREG_CFD); + if (!(cfg & (1 << 0))) + sio_data->has_fanin |= (1 << 1); /* FANIN1 */ + + cfg = superio_inb(sioaddr, SIOREG_CF4); + if (!(cfg & (1 << 0))) + sio_data->has_fanin |= (1 << 7); /* FANIN7 */ + cfg_b = superio_inb(sioaddr, SIOREG_CFB); + if (!(cfg & (1 << 1)) && (cfg_b & (1 << 3))) + sio_data->has_fanin |= (1 << 5); /* FANIN5 */ + cfg = superio_inb(sioaddr, SIOREG_CF3); + if ((cfg & (1 << 3)) && !(cfg_b & (1 << 5))) + sio_data->has_fanin |= (1 << 6); /* FANIN6 */ + + /* Check which fan outputs are wired */ + sio_data->has_fanout = (1 << 0); /* FANOUT0 */ + if (cfg_b & (1 << 0)) + sio_data->has_fanout |= (1 << 3); /* FANOUT3 */ + + cfg = superio_inb(sioaddr, SIOREG_CFC); + if (!(cfg & (1 << 4))) { + if (cfg_b & (1 << 1)) + sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ + if (cfg_b & (1 << 2)) + sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ + } + + /* FANOUT1 and FANOUT2 can each be routed to 2 different pins */ + cfg = superio_inb(sioaddr, SIOREG_CF5); + if (cfg & (1 << 6)) + sio_data->has_fanout |= (1 << 1); /* FANOUT1 */ + if (cfg & (1 << 5)) + sio_data->has_fanout |= (1 << 2); /* FANOUT2 */ + exit: superio_exit(sioaddr); return err; @@ -607,15 +1328,10 @@ exit: static int __init pc87427_init(void) { int err; - unsigned short address[2]; - - if (pc87427_find(0x2e, address) - && pc87427_find(0x4e, address)) - return -ENODEV; + struct pc87427_sio_data sio_data; - /* For now the driver only handles fans so we only care about the - first address. */ - if (!address[0]) + if (pc87427_find(0x2e, &sio_data) + && pc87427_find(0x4e, &sio_data)) return -ENODEV; err = platform_driver_register(&pc87427_driver); @@ -623,7 +1339,7 @@ static int __init pc87427_init(void) goto exit; /* Sets global pdev as a side effect */ - err = pc87427_device_add(address[0]); + err = pc87427_device_add(&sio_data); if (err) goto exit_driver; diff --git a/drivers/hwmon/pkgtemp.c b/drivers/hwmon/pkgtemp.c new file mode 100644 index 000000000000..74157fcda6ed --- /dev/null +++ b/drivers/hwmon/pkgtemp.c @@ -0,0 +1,456 @@ +/* + * pkgtemp.c - Linux kernel module for processor package hardware monitoring + * + * Copyright (C) 2010 Fenghua Yu <fenghua.yu@intel.com> + * + * Inspired from many hwmon drivers especially coretemp. + * + * 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; version 2 of the License. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/hwmon.h> +#include <linux/sysfs.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/list.h> +#include <linux/platform_device.h> +#include <linux/cpu.h> +#include <linux/pci.h> +#include <asm/msr.h> +#include <asm/processor.h> + +#define DRVNAME "pkgtemp" + +enum { SHOW_TEMP, SHOW_TJMAX, SHOW_TTARGET, SHOW_LABEL, SHOW_NAME }; + +/* + * Functions declaration + */ + +static struct pkgtemp_data *pkgtemp_update_device(struct device *dev); + +struct pkgtemp_data { + struct device *hwmon_dev; + struct mutex update_lock; + const char *name; + u32 id; + u16 phys_proc_id; + char valid; /* zero until following fields are valid */ + unsigned long last_updated; /* in jiffies */ + int temp; + int tjmax; + int ttarget; + u8 alarm; +}; + +/* + * Sysfs stuff + */ + +static ssize_t show_name(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + int ret; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pkgtemp_data *data = dev_get_drvdata(dev); + + if (attr->index == SHOW_NAME) + ret = sprintf(buf, "%s\n", data->name); + else /* show label */ + ret = sprintf(buf, "physical id %d\n", + data->phys_proc_id); + return ret; +} + +static ssize_t show_alarm(struct device *dev, struct device_attribute + *devattr, char *buf) +{ + struct pkgtemp_data *data = pkgtemp_update_device(dev); + /* read the Out-of-spec log, never clear */ + return sprintf(buf, "%d\n", data->alarm); +} + +static ssize_t show_temp(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct pkgtemp_data *data = pkgtemp_update_device(dev); + int err = 0; + + if (attr->index == SHOW_TEMP) + err = data->valid ? sprintf(buf, "%d\n", data->temp) : -EAGAIN; + else if (attr->index == SHOW_TJMAX) + err = sprintf(buf, "%d\n", data->tjmax); + else + err = sprintf(buf, "%d\n", data->ttarget); + return err; +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL, SHOW_TEMP); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp, NULL, SHOW_TJMAX); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, show_temp, NULL, SHOW_TTARGET); +static DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL); +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_name, NULL, SHOW_LABEL); +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, SHOW_NAME); + +static struct attribute *pkgtemp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &dev_attr_temp1_crit_alarm.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + NULL +}; + +static const struct attribute_group pkgtemp_group = { + .attrs = pkgtemp_attributes, +}; + +static struct pkgtemp_data *pkgtemp_update_device(struct device *dev) +{ + struct pkgtemp_data *data = dev_get_drvdata(dev); + unsigned int cpu; + int err; + + mutex_lock(&data->update_lock); + + if (!data->valid || time_after(jiffies, data->last_updated + HZ)) { + u32 eax, edx; + + data->valid = 0; + cpu = data->id; + err = rdmsr_on_cpu(cpu, MSR_IA32_PACKAGE_THERM_STATUS, + &eax, &edx); + if (!err) { + data->alarm = (eax >> 5) & 1; + data->temp = data->tjmax - (((eax >> 16) + & 0x7f) * 1000); + data->valid = 1; + } else + dev_dbg(dev, "Temperature data invalid (0x%x)\n", eax); + + data->last_updated = jiffies; + } + + mutex_unlock(&data->update_lock); + return data; +} + +static int get_tjmax(int cpu, struct device *dev) +{ + int default_tjmax = 100000; + int err; + u32 eax, edx; + u32 val; + + /* IA32_TEMPERATURE_TARGET contains the TjMax value */ + err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); + if (!err) { + val = (eax >> 16) & 0xff; + if ((val > 80) && (val < 120)) { + dev_info(dev, "TjMax is %d C.\n", val); + return val * 1000; + } + } + dev_warn(dev, "Unable to read TjMax from CPU.\n"); + return default_tjmax; +} + +static int __devinit pkgtemp_probe(struct platform_device *pdev) +{ + struct pkgtemp_data *data; + int err; + u32 eax, edx; +#ifdef CONFIG_SMP + struct cpuinfo_x86 *c = &cpu_data(pdev->id); +#endif + + data = kzalloc(sizeof(struct pkgtemp_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + dev_err(&pdev->dev, "Out of memory\n"); + goto exit; + } + + data->id = pdev->id; +#ifdef CONFIG_SMP + data->phys_proc_id = c->phys_proc_id; +#endif + data->name = "pkgtemp"; + mutex_init(&data->update_lock); + + /* test if we can access the THERM_STATUS MSR */ + err = rdmsr_safe_on_cpu(data->id, MSR_IA32_PACKAGE_THERM_STATUS, + &eax, &edx); + if (err) { + dev_err(&pdev->dev, + "Unable to access THERM_STATUS MSR, giving up\n"); + goto exit_free; + } + + data->tjmax = get_tjmax(data->id, &pdev->dev); + platform_set_drvdata(pdev, data); + + err = rdmsr_safe_on_cpu(data->id, MSR_IA32_TEMPERATURE_TARGET, + &eax, &edx); + if (err) { + dev_warn(&pdev->dev, "Unable to read" + " IA32_TEMPERATURE_TARGET MSR\n"); + } else { + data->ttarget = data->tjmax - (((eax >> 8) & 0xff) * 1000); + err = device_create_file(&pdev->dev, + &sensor_dev_attr_temp1_max.dev_attr); + if (err) + goto exit_free; + } + + err = sysfs_create_group(&pdev->dev.kobj, &pkgtemp_group); + if (err) + goto exit_free; + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", + err); + goto exit_class; + } + + return 0; + +exit_class: + sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group); +exit_free: + kfree(data); +exit: + return err; +} + +static int __devexit pkgtemp_remove(struct platform_device *pdev) +{ + struct pkgtemp_data *data = platform_get_drvdata(pdev); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &pkgtemp_group); + platform_set_drvdata(pdev, NULL); + kfree(data); + return 0; +} + +static struct platform_driver pkgtemp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = DRVNAME, + }, + .probe = pkgtemp_probe, + .remove = __devexit_p(pkgtemp_remove), +}; + +struct pdev_entry { + struct list_head list; + struct platform_device *pdev; + unsigned int cpu; +#ifdef CONFIG_SMP + u16 phys_proc_id; +#endif +}; + +static LIST_HEAD(pdev_list); +static DEFINE_MUTEX(pdev_list_mutex); + +static int __cpuinit pkgtemp_device_add(unsigned int cpu) +{ + int err; + struct platform_device *pdev; + struct pdev_entry *pdev_entry; +#ifdef CONFIG_SMP + struct cpuinfo_x86 *c = &cpu_data(cpu); +#endif + + mutex_lock(&pdev_list_mutex); + +#ifdef CONFIG_SMP + /* Only keep the first entry in each package */ + list_for_each_entry(pdev_entry, &pdev_list, list) { + if (c->phys_proc_id == pdev_entry->phys_proc_id) { + err = 0; /* Not an error */ + goto exit; + } + } +#endif + + pdev = platform_device_alloc(DRVNAME, cpu); + if (!pdev) { + err = -ENOMEM; + printk(KERN_ERR DRVNAME ": Device allocation failed\n"); + goto exit; + } + + pdev_entry = kzalloc(sizeof(struct pdev_entry), GFP_KERNEL); + if (!pdev_entry) { + err = -ENOMEM; + goto exit_device_put; + } + + err = platform_device_add(pdev); + if (err) { + printk(KERN_ERR DRVNAME ": Device addition failed (%d)\n", + err); + goto exit_device_free; + } + +#ifdef CONFIG_SMP + pdev_entry->phys_proc_id = c->phys_proc_id; +#endif + pdev_entry->pdev = pdev; + pdev_entry->cpu = cpu; + list_add_tail(&pdev_entry->list, &pdev_list); + mutex_unlock(&pdev_list_mutex); + + return 0; + +exit_device_free: + kfree(pdev_entry); +exit_device_put: + platform_device_put(pdev); +exit: + mutex_unlock(&pdev_list_mutex); + return err; +} + +#ifdef CONFIG_HOTPLUG_CPU +static void pkgtemp_device_remove(unsigned int cpu) +{ + struct pdev_entry *p, *n; + unsigned int i; + int err; + + mutex_lock(&pdev_list_mutex); + list_for_each_entry_safe(p, n, &pdev_list, list) { + if (p->cpu != cpu) + continue; + + platform_device_unregister(p->pdev); + list_del(&p->list); + kfree(p); + for_each_cpu(i, cpu_core_mask(cpu)) { + if (i != cpu) { + err = pkgtemp_device_add(i); + if (!err) + break; + } + } + break; + } + mutex_unlock(&pdev_list_mutex); +} + +static int __cpuinit pkgtemp_cpu_callback(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + unsigned int cpu = (unsigned long) hcpu; + + switch (action) { + case CPU_ONLINE: + case CPU_DOWN_FAILED: + pkgtemp_device_add(cpu); + break; + case CPU_DOWN_PREPARE: + pkgtemp_device_remove(cpu); + break; + } + return NOTIFY_OK; +} + +static struct notifier_block pkgtemp_cpu_notifier __refdata = { + .notifier_call = pkgtemp_cpu_callback, +}; +#endif /* !CONFIG_HOTPLUG_CPU */ + +static int __init pkgtemp_init(void) +{ + int i, err = -ENODEV; + struct pdev_entry *p, *n; + + /* quick check if we run Intel */ + if (cpu_data(0).x86_vendor != X86_VENDOR_INTEL) + goto exit; + + err = platform_driver_register(&pkgtemp_driver); + if (err) + goto exit; + + for_each_online_cpu(i) { + struct cpuinfo_x86 *c = &cpu_data(i); + + if (!cpu_has(c, X86_FEATURE_PTS)) + continue; + + err = pkgtemp_device_add(i); + if (err) + goto exit_devices_unreg; + } + if (list_empty(&pdev_list)) { + err = -ENODEV; + goto exit_driver_unreg; + } + +#ifdef CONFIG_HOTPLUG_CPU + register_hotcpu_notifier(&pkgtemp_cpu_notifier); +#endif + return 0; + +exit_devices_unreg: + mutex_lock(&pdev_list_mutex); + list_for_each_entry_safe(p, n, &pdev_list, list) { + platform_device_unregister(p->pdev); + list_del(&p->list); + kfree(p); + } + mutex_unlock(&pdev_list_mutex); +exit_driver_unreg: + platform_driver_unregister(&pkgtemp_driver); +exit: + return err; +} + +static void __exit pkgtemp_exit(void) +{ + struct pdev_entry *p, *n; +#ifdef CONFIG_HOTPLUG_CPU + unregister_hotcpu_notifier(&pkgtemp_cpu_notifier); +#endif + mutex_lock(&pdev_list_mutex); + list_for_each_entry_safe(p, n, &pdev_list, list) { + platform_device_unregister(p->pdev); + list_del(&p->list); + kfree(p); + } + mutex_unlock(&pdev_list_mutex); + platform_driver_unregister(&pkgtemp_driver); +} + +MODULE_AUTHOR("Fenghua Yu <fenghua.yu@intel.com>"); +MODULE_DESCRIPTION("Intel processor package temperature monitor"); +MODULE_LICENSE("GPL"); + +module_init(pkgtemp_init) +module_exit(pkgtemp_exit) diff --git a/drivers/hwmon/smm665.c b/drivers/hwmon/smm665.c new file mode 100644 index 000000000000..425df5bccd45 --- /dev/null +++ b/drivers/hwmon/smm665.c @@ -0,0 +1,743 @@ +/* + * Driver for SMM665 Power Controller / Monitor + * + * Copyright (C) 2010 Ericsson AB. + * + * 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; version 2 of the License. + * + * This driver should also work for SMM465, SMM764, and SMM766, but is untested + * for those chips. Only monitoring functionality is implemented. + * + * Datasheets: + * http://www.summitmicro.com/prod_select/summary/SMM665/SMM665B_2089_20.pdf + * http://www.summitmicro.com/prod_select/summary/SMM766B/SMM766B_2122.pdf + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/delay.h> + +/* Internal reference voltage (VREF, x 1000 */ +#define SMM665_VREF_ADC_X1000 1250 + +/* module parameters */ +static int vref = SMM665_VREF_ADC_X1000; +module_param(vref, int, 0); +MODULE_PARM_DESC(vref, "Reference voltage in mV"); + +enum chips { smm465, smm665, smm665c, smm764, smm766 }; + +/* + * ADC channel addresses + */ +#define SMM665_MISC16_ADC_DATA_A 0x00 +#define SMM665_MISC16_ADC_DATA_B 0x01 +#define SMM665_MISC16_ADC_DATA_C 0x02 +#define SMM665_MISC16_ADC_DATA_D 0x03 +#define SMM665_MISC16_ADC_DATA_E 0x04 +#define SMM665_MISC16_ADC_DATA_F 0x05 +#define SMM665_MISC16_ADC_DATA_VDD 0x06 +#define SMM665_MISC16_ADC_DATA_12V 0x07 +#define SMM665_MISC16_ADC_DATA_INT_TEMP 0x08 +#define SMM665_MISC16_ADC_DATA_AIN1 0x09 +#define SMM665_MISC16_ADC_DATA_AIN2 0x0a + +/* + * Command registers + */ +#define SMM665_MISC8_CMD_STS 0x80 +#define SMM665_MISC8_STATUS1 0x81 +#define SMM665_MISC8_STATUSS2 0x82 +#define SMM665_MISC8_IO_POLARITY 0x83 +#define SMM665_MISC8_PUP_POLARITY 0x84 +#define SMM665_MISC8_ADOC_STATUS1 0x85 +#define SMM665_MISC8_ADOC_STATUS2 0x86 +#define SMM665_MISC8_WRITE_PROT 0x87 +#define SMM665_MISC8_STS_TRACK 0x88 + +/* + * Configuration registers and register groups + */ +#define SMM665_ADOC_ENABLE 0x0d +#define SMM665_LIMIT_BASE 0x80 /* First limit register */ + +/* + * Limit register bit masks + */ +#define SMM665_TRIGGER_RST 0x8000 +#define SMM665_TRIGGER_HEALTHY 0x4000 +#define SMM665_TRIGGER_POWEROFF 0x2000 +#define SMM665_TRIGGER_SHUTDOWN 0x1000 +#define SMM665_ADC_MASK 0x03ff + +#define smm665_is_critical(lim) ((lim) & (SMM665_TRIGGER_RST \ + | SMM665_TRIGGER_POWEROFF \ + | SMM665_TRIGGER_SHUTDOWN)) +/* + * Fault register bit definitions + * Values are merged from status registers 1/2, + * with status register 1 providing the upper 8 bits. + */ +#define SMM665_FAULT_A 0x0001 +#define SMM665_FAULT_B 0x0002 +#define SMM665_FAULT_C 0x0004 +#define SMM665_FAULT_D 0x0008 +#define SMM665_FAULT_E 0x0010 +#define SMM665_FAULT_F 0x0020 +#define SMM665_FAULT_VDD 0x0040 +#define SMM665_FAULT_12V 0x0080 +#define SMM665_FAULT_TEMP 0x0100 +#define SMM665_FAULT_AIN1 0x0200 +#define SMM665_FAULT_AIN2 0x0400 + +/* + * I2C Register addresses + * + * The configuration register needs to be the configured base register. + * The command/status register address is derived from it. + */ +#define SMM665_REGMASK 0x78 +#define SMM665_CMDREG_BASE 0x48 +#define SMM665_CONFREG_BASE 0x50 + +/* + * Equations given by chip manufacturer to calculate voltage/temperature values + * vref = Reference voltage on VREF_ADC pin (module parameter) + * adc = 10bit ADC value read back from registers + */ + +/* Voltage A-F and VDD */ +#define SMM665_VMON_ADC_TO_VOLTS(adc) ((adc) * vref / 256) + +/* Voltage 12VIN */ +#define SMM665_12VIN_ADC_TO_VOLTS(adc) ((adc) * vref * 3 / 256) + +/* Voltage AIN1, AIN2 */ +#define SMM665_AIN_ADC_TO_VOLTS(adc) ((adc) * vref / 512) + +/* Temp Sensor */ +#define SMM665_TEMP_ADC_TO_CELSIUS(adc) ((adc) <= 511) ? \ + ((int)(adc) * 1000 / 4) : \ + (((int)(adc) - 0x400) * 1000 / 4) + +#define SMM665_NUM_ADC 11 + +/* + * Chip dependent ADC conversion time, in uS + */ +#define SMM665_ADC_WAIT_SMM665 70 +#define SMM665_ADC_WAIT_SMM766 185 + +struct smm665_data { + enum chips type; + int conversion_time; /* ADC conversion time */ + struct device *hwmon_dev; + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + u16 adc[SMM665_NUM_ADC]; /* adc values (raw) */ + u16 faults; /* fault status */ + /* The following values are in mV */ + int critical_min_limit[SMM665_NUM_ADC]; + int alarm_min_limit[SMM665_NUM_ADC]; + int critical_max_limit[SMM665_NUM_ADC]; + int alarm_max_limit[SMM665_NUM_ADC]; + struct i2c_client *cmdreg; +}; + +/* + * smm665_read16() + * + * Read 16 bit value from <reg>, <reg+1>. Upper 8 bits are in <reg>. + */ +static int smm665_read16(struct i2c_client *client, int reg) +{ + int rv, val; + + rv = i2c_smbus_read_byte_data(client, reg); + if (rv < 0) + return rv; + val = rv << 8; + rv = i2c_smbus_read_byte_data(client, reg + 1); + if (rv < 0) + return rv; + val |= rv; + return val; +} + +/* + * Read adc value. + */ +static int smm665_read_adc(struct smm665_data *data, int adc) +{ + struct i2c_client *client = data->cmdreg; + int rv; + int radc; + + /* + * Algorithm for reading ADC, per SMM665 datasheet + * + * {[S][addr][W][Ack]} {[offset][Ack]} {[S][addr][R][Nack]} + * [wait conversion time] + * {[S][addr][R][Ack]} {[datahi][Ack]} {[datalo][Ack][P]} + * + * To implement the first part of this exchange, + * do a full read transaction and expect a failure/Nack. + * This sets up the address pointer on the SMM665 + * and starts the ADC conversion. + * Then do a two-byte read transaction. + */ + rv = i2c_smbus_read_byte_data(client, adc << 3); + if (rv != -ENXIO) { + /* + * We expect ENXIO to reflect NACK + * (per Documentation/i2c/fault-codes). + * Everything else is an error. + */ + dev_dbg(&client->dev, + "Unexpected return code %d when setting ADC index", rv); + return (rv < 0) ? rv : -EIO; + } + + udelay(data->conversion_time); + + /* + * Now read two bytes. + * + * Neither i2c_smbus_read_byte() nor + * i2c_smbus_read_block_data() worked here, + * so use i2c_smbus_read_word_data() instead. + * We could also try to use i2c_master_recv(), + * but that is not always supported. + */ + rv = i2c_smbus_read_word_data(client, 0); + if (rv < 0) { + dev_dbg(&client->dev, "Failed to read ADC value: error %d", rv); + return -1; + } + /* + * Validate/verify readback adc channel (in bit 11..14). + * High byte is in lower 8 bit of rv, so only shift by 3. + */ + radc = (rv >> 3) & 0x0f; + if (radc != adc) { + dev_dbg(&client->dev, "Unexpected RADC: Expected %d got %d", + adc, radc); + return -EIO; + } + /* + * Chip replies with H/L, while SMBus expects L/H. + * Thus, byte order is reversed, and we have to swap + * the result. + */ + rv = swab16(rv) & SMM665_ADC_MASK; + + return rv; +} + +static struct smm665_data *smm665_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smm665_data *data = i2c_get_clientdata(client); + struct smm665_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i, val; + + /* + * read status registers + */ + val = smm665_read16(client, SMM665_MISC8_STATUS1); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->faults = val; + + /* Read adc registers */ + for (i = 0; i < SMM665_NUM_ADC; i++) { + val = smm665_read_adc(data, i); + if (unlikely(val < 0)) { + ret = ERR_PTR(val); + goto abort; + } + data->adc[i] = val; + } + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +/* Return converted value from given adc */ +static int smm665_convert(u16 adcval, int index) +{ + int val = 0; + + switch (index) { + case SMM665_MISC16_ADC_DATA_12V: + val = SMM665_12VIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_VDD: + case SMM665_MISC16_ADC_DATA_A: + case SMM665_MISC16_ADC_DATA_B: + case SMM665_MISC16_ADC_DATA_C: + case SMM665_MISC16_ADC_DATA_D: + case SMM665_MISC16_ADC_DATA_E: + case SMM665_MISC16_ADC_DATA_F: + val = SMM665_VMON_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_AIN1: + case SMM665_MISC16_ADC_DATA_AIN2: + val = SMM665_AIN_ADC_TO_VOLTS(adcval & SMM665_ADC_MASK); + break; + + case SMM665_MISC16_ADC_DATA_INT_TEMP: + val = SMM665_TEMP_ADC_TO_CELSIUS(adcval & SMM665_ADC_MASK); + break; + + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return val; +} + +static int smm665_get_min(struct device *dev, int index) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smm665_data *data = i2c_get_clientdata(client); + + return data->alarm_min_limit[index]; +} + +static int smm665_get_max(struct device *dev, int index) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smm665_data *data = i2c_get_clientdata(client); + + return data->alarm_max_limit[index]; +} + +static int smm665_get_lcrit(struct device *dev, int index) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smm665_data *data = i2c_get_clientdata(client); + + return data->critical_min_limit[index]; +} + +static int smm665_get_crit(struct device *dev, int index) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smm665_data *data = i2c_get_clientdata(client); + + return data->critical_max_limit[index]; +} + +static ssize_t smm665_show_crit_alarm(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct smm665_data *data = smm665_update_device(dev); + int val = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + if (data->faults & (1 << attr->index)) + val = 1; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t smm665_show_input(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct smm665_data *data = smm665_update_device(dev); + int adc = attr->index; + int val; + + if (IS_ERR(data)) + return PTR_ERR(data); + + val = smm665_convert(data->adc[adc], adc); + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +#define SMM665_SHOW(what) \ + static ssize_t smm665_show_##what(struct device *dev, \ + struct device_attribute *da, char *buf) \ +{ \ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); \ + const int val = smm665_get_##what(dev, attr->index); \ + return snprintf(buf, PAGE_SIZE, "%d\n", val); \ +} + +SMM665_SHOW(min); +SMM665_SHOW(max); +SMM665_SHOW(lcrit); +SMM665_SHOW(crit); + +/* These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +#define SMM665_ATTR(name, type, cmd_idx) \ + static SENSOR_DEVICE_ATTR(name##_##type, S_IRUGO, \ + smm665_show_##type, NULL, cmd_idx) + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +SMM665_ATTR(in1, input, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, input, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, input, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, input, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, input, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, input, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, input, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, input, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, input, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, input, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages min */ +SMM665_ATTR(in1, min, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, min, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, min, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, min, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, min, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, min, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, min, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, min, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, min, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, min, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages max */ +SMM665_ATTR(in1, max, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, max, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, max, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, max, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, max, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, max, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, max, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, max, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, max, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, max, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages lcrit */ +SMM665_ATTR(in1, lcrit, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, lcrit, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, lcrit, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, lcrit, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, lcrit, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, lcrit, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, lcrit, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, lcrit, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, lcrit, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, lcrit, SMM665_MISC16_ADC_DATA_AIN2); + +/* Input voltages crit */ +SMM665_ATTR(in1, crit, SMM665_MISC16_ADC_DATA_12V); +SMM665_ATTR(in2, crit, SMM665_MISC16_ADC_DATA_VDD); +SMM665_ATTR(in3, crit, SMM665_MISC16_ADC_DATA_A); +SMM665_ATTR(in4, crit, SMM665_MISC16_ADC_DATA_B); +SMM665_ATTR(in5, crit, SMM665_MISC16_ADC_DATA_C); +SMM665_ATTR(in6, crit, SMM665_MISC16_ADC_DATA_D); +SMM665_ATTR(in7, crit, SMM665_MISC16_ADC_DATA_E); +SMM665_ATTR(in8, crit, SMM665_MISC16_ADC_DATA_F); +SMM665_ATTR(in9, crit, SMM665_MISC16_ADC_DATA_AIN1); +SMM665_ATTR(in10, crit, SMM665_MISC16_ADC_DATA_AIN2); + +/* critical alarms */ +SMM665_ATTR(in1, crit_alarm, SMM665_FAULT_12V); +SMM665_ATTR(in2, crit_alarm, SMM665_FAULT_VDD); +SMM665_ATTR(in3, crit_alarm, SMM665_FAULT_A); +SMM665_ATTR(in4, crit_alarm, SMM665_FAULT_B); +SMM665_ATTR(in5, crit_alarm, SMM665_FAULT_C); +SMM665_ATTR(in6, crit_alarm, SMM665_FAULT_D); +SMM665_ATTR(in7, crit_alarm, SMM665_FAULT_E); +SMM665_ATTR(in8, crit_alarm, SMM665_FAULT_F); +SMM665_ATTR(in9, crit_alarm, SMM665_FAULT_AIN1); +SMM665_ATTR(in10, crit_alarm, SMM665_FAULT_AIN2); + +/* Temperature */ +SMM665_ATTR(temp1, input, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, min, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, max, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, lcrit, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, crit, SMM665_MISC16_ADC_DATA_INT_TEMP); +SMM665_ATTR(temp1, crit_alarm, SMM665_FAULT_TEMP); + +/* + * Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *smm665_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min.dev_attr.attr, + &sensor_dev_attr_in1_max.dev_attr.attr, + &sensor_dev_attr_in1_lcrit.dev_attr.attr, + &sensor_dev_attr_in1_crit.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in2_min.dev_attr.attr, + &sensor_dev_attr_in2_max.dev_attr.attr, + &sensor_dev_attr_in2_lcrit.dev_attr.attr, + &sensor_dev_attr_in2_crit.dev_attr.attr, + &sensor_dev_attr_in2_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in3_min.dev_attr.attr, + &sensor_dev_attr_in3_max.dev_attr.attr, + &sensor_dev_attr_in3_lcrit.dev_attr.attr, + &sensor_dev_attr_in3_crit.dev_attr.attr, + &sensor_dev_attr_in3_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in4_input.dev_attr.attr, + &sensor_dev_attr_in4_min.dev_attr.attr, + &sensor_dev_attr_in4_max.dev_attr.attr, + &sensor_dev_attr_in4_lcrit.dev_attr.attr, + &sensor_dev_attr_in4_crit.dev_attr.attr, + &sensor_dev_attr_in4_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in5_min.dev_attr.attr, + &sensor_dev_attr_in5_max.dev_attr.attr, + &sensor_dev_attr_in5_lcrit.dev_attr.attr, + &sensor_dev_attr_in5_crit.dev_attr.attr, + &sensor_dev_attr_in5_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in6_min.dev_attr.attr, + &sensor_dev_attr_in6_max.dev_attr.attr, + &sensor_dev_attr_in6_lcrit.dev_attr.attr, + &sensor_dev_attr_in6_crit.dev_attr.attr, + &sensor_dev_attr_in6_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in7_min.dev_attr.attr, + &sensor_dev_attr_in7_max.dev_attr.attr, + &sensor_dev_attr_in7_lcrit.dev_attr.attr, + &sensor_dev_attr_in7_crit.dev_attr.attr, + &sensor_dev_attr_in7_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in8_input.dev_attr.attr, + &sensor_dev_attr_in8_min.dev_attr.attr, + &sensor_dev_attr_in8_max.dev_attr.attr, + &sensor_dev_attr_in8_lcrit.dev_attr.attr, + &sensor_dev_attr_in8_crit.dev_attr.attr, + &sensor_dev_attr_in8_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in9_min.dev_attr.attr, + &sensor_dev_attr_in9_max.dev_attr.attr, + &sensor_dev_attr_in9_lcrit.dev_attr.attr, + &sensor_dev_attr_in9_crit.dev_attr.attr, + &sensor_dev_attr_in9_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in10_min.dev_attr.attr, + &sensor_dev_attr_in10_max.dev_attr.attr, + &sensor_dev_attr_in10_lcrit.dev_attr.attr, + &sensor_dev_attr_in10_crit.dev_attr.attr, + &sensor_dev_attr_in10_crit_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_lcrit.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group smm665_group = { + .attrs = smm665_attributes, +}; + +static int smm665_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct smm665_data *data; + int i, ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + if (i2c_smbus_read_byte_data(client, SMM665_ADOC_ENABLE) < 0) + return -ENODEV; + + ret = -ENOMEM; + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + goto out_return; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + data->type = id->driver_data; + data->cmdreg = i2c_new_dummy(adapter, (client->addr & ~SMM665_REGMASK) + | SMM665_CMDREG_BASE); + if (!data->cmdreg) + goto out_kfree; + + switch (data->type) { + case smm465: + case smm665: + data->conversion_time = SMM665_ADC_WAIT_SMM665; + break; + case smm665c: + case smm764: + case smm766: + data->conversion_time = SMM665_ADC_WAIT_SMM766; + break; + } + + ret = -ENODEV; + if (i2c_smbus_read_byte_data(data->cmdreg, SMM665_MISC8_CMD_STS) < 0) + goto out_unregister; + + /* + * Read limits. + * + * Limit registers start with register SMM665_LIMIT_BASE. + * Each channel uses 8 registers, providing four limit values + * per channel. Each limit value requires two registers, with the + * high byte in the first register and the low byte in the second + * register. The first two limits are under limit values, followed + * by two over limit values. + * + * Limit register order matches the ADC register order, so we use + * ADC register defines throughout the code to index limit registers. + * + * We save the first retrieved value both as "critical" and "alarm" + * value. The second value overwrites either the critical or the + * alarm value, depending on its configuration. This ensures that both + * critical and alarm values are initialized, even if both registers are + * configured as critical or non-critical. + */ + for (i = 0; i < SMM665_NUM_ADC; i++) { + int val; + + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8); + if (unlikely(val < 0)) + goto out_unregister; + data->critical_min_limit[i] = data->alarm_min_limit[i] + = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 2); + if (unlikely(val < 0)) + goto out_unregister; + if (smm665_is_critical(val)) + data->critical_min_limit[i] = smm665_convert(val, i); + else + data->alarm_min_limit[i] = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 4); + if (unlikely(val < 0)) + goto out_unregister; + data->critical_max_limit[i] = data->alarm_max_limit[i] + = smm665_convert(val, i); + val = smm665_read16(client, SMM665_LIMIT_BASE + i * 8 + 6); + if (unlikely(val < 0)) + goto out_unregister; + if (smm665_is_critical(val)) + data->critical_max_limit[i] = smm665_convert(val, i); + else + data->alarm_max_limit[i] = smm665_convert(val, i); + } + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, &smm665_group); + if (ret) + goto out_unregister; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_remove_group; + } + + return 0; + +out_remove_group: + sysfs_remove_group(&client->dev.kobj, &smm665_group); +out_unregister: + i2c_unregister_device(data->cmdreg); +out_kfree: + kfree(data); +out_return: + return ret; +} + +static int smm665_remove(struct i2c_client *client) +{ + struct smm665_data *data = i2c_get_clientdata(client); + + i2c_unregister_device(data->cmdreg); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &smm665_group); + + kfree(data); + + return 0; +} + +static const struct i2c_device_id smm665_id[] = { + {"smm465", smm465}, + {"smm665", smm665}, + {"smm665c", smm665c}, + {"smm764", smm764}, + {"smm766", smm766}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, smm665_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver smm665_driver = { + .driver = { + .name = "smm665", + }, + .probe = smm665_probe, + .remove = smm665_remove, + .id_table = smm665_id, +}; + +static int __init smm665_init(void) +{ + return i2c_add_driver(&smm665_driver); +} + +static void __exit smm665_exit(void) +{ + i2c_del_driver(&smm665_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("SMM665 driver"); +MODULE_LICENSE("GPL"); + +module_init(smm665_init); +module_exit(smm665_exit); diff --git a/drivers/hwmon/ultra45_env.c b/drivers/hwmon/ultra45_env.c index 5da5942cf970..d863e13a50b8 100644 --- a/drivers/hwmon/ultra45_env.c +++ b/drivers/hwmon/ultra45_env.c @@ -234,7 +234,7 @@ static const struct attribute_group env_group = { .attrs = env_attributes, }; -static int __devinit env_probe(struct of_device *op, +static int __devinit env_probe(struct platform_device *op, const struct of_device_id *match) { struct env *p = kzalloc(sizeof(*p), GFP_KERNEL); @@ -276,7 +276,7 @@ out_free: goto out; } -static int __devexit env_remove(struct of_device *op) +static int __devexit env_remove(struct platform_device *op) { struct env *p = dev_get_drvdata(&op->dev); @@ -311,12 +311,12 @@ static struct of_platform_driver env_driver = { static int __init env_init(void) { - return of_register_driver(&env_driver, &of_bus_type); + return of_register_platform_driver(&env_driver); } static void __exit env_exit(void) { - of_unregister_driver(&env_driver); + of_unregister_platform_driver(&env_driver); } module_init(env_init); diff --git a/drivers/hwmon/via-cputemp.c b/drivers/hwmon/via-cputemp.c index 7442cf754856..ffb793af680b 100644 --- a/drivers/hwmon/via-cputemp.c +++ b/drivers/hwmon/via-cputemp.c @@ -39,7 +39,7 @@ #define DRVNAME "via_cputemp" -enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME } SHOW; +enum { SHOW_TEMP, SHOW_LABEL, SHOW_NAME }; /* * Functions declaration diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 0dcaba9b7189..e96e69dd36fb 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -39,6 +39,7 @@ w83627dhg 9 5 4 3 0xa020 0xc1 0x5ca3 w83627dhg-p 9 5 4 3 0xb070 0xc1 0x5ca3 w83667hg 9 5 3 3 0xa510 0xc1 0x5ca3 + w83667hg-b 9 5 3 3 0xb350 0xc1 0x5ca3 */ #include <linux/module.h> @@ -55,7 +56,7 @@ #include <linux/io.h> #include "lm75.h" -enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg }; +enum kinds { w83627ehf, w83627dhg, w83627dhg_p, w83667hg, w83667hg_b }; /* used to set data->name = w83627ehf_device_names[data->sio_kind] */ static const char * w83627ehf_device_names[] = { @@ -63,6 +64,7 @@ static const char * w83627ehf_device_names[] = { "w83627dhg", "w83627dhg", "w83667hg", + "w83667hg", }; static unsigned short force_id; @@ -91,6 +93,7 @@ MODULE_PARM_DESC(force_id, "Override the detected device ID"); #define SIO_W83627DHG_ID 0xa020 #define SIO_W83627DHG_P_ID 0xb070 #define SIO_W83667HG_ID 0xa510 +#define SIO_W83667HG_B_ID 0xb350 #define SIO_ID_MASK 0xFFF0 static inline void @@ -201,8 +204,14 @@ static const u8 W83627EHF_REG_TOLERANCE[] = { 0x07, 0x07, 0x14, 0x62 }; static const u8 W83627EHF_REG_FAN_START_OUTPUT[] = { 0x0a, 0x0b, 0x16, 0x65 }; static const u8 W83627EHF_REG_FAN_STOP_OUTPUT[] = { 0x08, 0x09, 0x15, 0x64 }; static const u8 W83627EHF_REG_FAN_STOP_TIME[] = { 0x0c, 0x0d, 0x17, 0x66 }; -static const u8 W83627EHF_REG_FAN_MAX_OUTPUT[] = { 0xff, 0x67, 0xff, 0x69 }; -static const u8 W83627EHF_REG_FAN_STEP_OUTPUT[] = { 0xff, 0x68, 0xff, 0x6a }; + +static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_COMMON[] + = { 0xff, 0x67, 0xff, 0x69 }; +static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_COMMON[] + = { 0xff, 0x68, 0xff, 0x6a }; + +static const u8 W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B[] = { 0x67, 0x69, 0x6b }; +static const u8 W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B[] = { 0x68, 0x6a, 0x6c }; /* * Conversions @@ -277,6 +286,11 @@ struct w83627ehf_data { struct device *hwmon_dev; struct mutex lock; + const u8 *REG_FAN_START_OUTPUT; + const u8 *REG_FAN_STOP_OUTPUT; + const u8 *REG_FAN_MAX_OUTPUT; + const u8 *REG_FAN_STEP_OUTPUT; + struct mutex update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ @@ -524,7 +538,10 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } } - for (i = 0; i < 4; i++) { + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_fan & (1 << i))) + continue; + /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ if (i != 1) { pwmcfg = w83627ehf_read_value(data, @@ -546,6 +563,17 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) W83627EHF_REG_FAN_STOP_OUTPUT[i]); data->fan_stop_time[i] = w83627ehf_read_value(data, W83627EHF_REG_FAN_STOP_TIME[i]); + + if (data->REG_FAN_MAX_OUTPUT[i] != 0xff) + data->fan_max_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_MAX_OUTPUT[i]); + + if (data->REG_FAN_STEP_OUTPUT[i] != 0xff) + data->fan_step_output[i] = + w83627ehf_read_value(data, + data->REG_FAN_STEP_OUTPUT[i]); + data->target_temp[i] = w83627ehf_read_value(data, W83627EHF_REG_TARGET[i]) & @@ -1126,7 +1154,7 @@ store_##reg(struct device *dev, struct device_attribute *attr, \ u32 val = SENSORS_LIMIT(simple_strtoul(buf, NULL, 10), 1, 255); \ mutex_lock(&data->update_lock); \ data->reg[nr] = val; \ - w83627ehf_write_value(data, W83627EHF_REG_##REG[nr], val); \ + w83627ehf_write_value(data, data->REG_##REG[nr], val); \ mutex_unlock(&data->update_lock); \ return count; \ } @@ -1206,12 +1234,26 @@ static struct sensor_device_attribute sda_sf3_arrays[] = { store_fan_stop_output, 1), SENSOR_ATTR(pwm3_stop_output, S_IWUSR | S_IRUGO, show_fan_stop_output, store_fan_stop_output, 2), +}; - /* pwm1 and pwm3 don't support max and step settings */ + +/* + * pwm1 and pwm3 don't support max and step settings on all chips. + * Need to check support while generating/removing attribute files. + */ +static struct sensor_device_attribute sda_sf3_max_step_arrays[] = { + SENSOR_ATTR(pwm1_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 0), + SENSOR_ATTR(pwm1_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 0), SENSOR_ATTR(pwm2_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, store_fan_max_output, 1), SENSOR_ATTR(pwm2_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, store_fan_step_output, 1), + SENSOR_ATTR(pwm3_max_output, S_IWUSR | S_IRUGO, show_fan_max_output, + store_fan_max_output, 2), + SENSOR_ATTR(pwm3_step_output, S_IWUSR | S_IRUGO, show_fan_step_output, + store_fan_step_output, 2), }; static ssize_t @@ -1235,6 +1277,12 @@ static void w83627ehf_device_remove_files(struct device *dev) for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays); i++) device_remove_file(dev, &sda_sf3_arrays[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) + device_remove_file(dev, &attr->dev_attr); + } for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) device_remove_file(dev, &sda_sf3_arrays_fan4[i].dev_attr); for (i = 0; i < data->in_num; i++) { @@ -1343,22 +1391,37 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) /* 627EHG and 627EHF have 10 voltage inputs; 627DHG and 667HG have 9 */ data->in_num = (sio_data->kind == w83627ehf) ? 10 : 9; /* 667HG has 3 pwms */ - data->pwm_num = (sio_data->kind == w83667hg) ? 3 : 4; + data->pwm_num = (sio_data->kind == w83667hg + || sio_data->kind == w83667hg_b) ? 3 : 4; /* Check temp3 configuration bit for 667HG */ - if (sio_data->kind == w83667hg) { + if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { data->temp3_disable = w83627ehf_read_value(data, W83627EHF_REG_TEMP_CONFIG[1]) & 0x01; data->in6_skip = !data->temp3_disable; } + data->REG_FAN_START_OUTPUT = W83627EHF_REG_FAN_START_OUTPUT; + data->REG_FAN_STOP_OUTPUT = W83627EHF_REG_FAN_STOP_OUTPUT; + if (sio_data->kind == w83667hg_b) { + data->REG_FAN_MAX_OUTPUT = + W83627EHF_REG_FAN_MAX_OUTPUT_W83667_B; + data->REG_FAN_STEP_OUTPUT = + W83627EHF_REG_FAN_STEP_OUTPUT_W83667_B; + } else { + data->REG_FAN_MAX_OUTPUT = + W83627EHF_REG_FAN_MAX_OUTPUT_COMMON; + data->REG_FAN_STEP_OUTPUT = + W83627EHF_REG_FAN_STEP_OUTPUT_COMMON; + } + /* Initialize the chip */ w83627ehf_init_device(data); data->vrm = vid_which_vrm(); superio_enter(sio_data->sioreg); /* Read VID value */ - if (sio_data->kind == w83667hg) { + if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { /* W83667HG has different pins for VID input and output, so we can get the VID input values directly at logical device D 0xe3. */ @@ -1409,7 +1472,7 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) } /* fan4 and fan5 share some pins with the GPIO and serial flash */ - if (sio_data->kind == w83667hg) { + if (sio_data->kind == w83667hg || sio_data->kind == w83667hg_b) { fan5pin = superio_inb(sio_data->sioreg, 0x27) & 0x20; fan4pin = superio_inb(sio_data->sioreg, 0x27) & 0x40; } else { @@ -1440,6 +1503,15 @@ static int __devinit w83627ehf_probe(struct platform_device *pdev) &sda_sf3_arrays[i].dev_attr))) goto exit_remove; + for (i = 0; i < ARRAY_SIZE(sda_sf3_max_step_arrays); i++) { + struct sensor_device_attribute *attr = + &sda_sf3_max_step_arrays[i]; + if (data->REG_FAN_STEP_OUTPUT[attr->index] != 0xff) { + err = device_create_file(dev, &attr->dev_attr); + if (err) + goto exit_remove; + } + } /* if fan4 is enabled create the sf3 files for it */ if ((data->has_fan & (1 << 3)) && data->pwm_num >= 4) for (i = 0; i < ARRAY_SIZE(sda_sf3_arrays_fan4); i++) { @@ -1556,6 +1628,7 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, static const char __initdata sio_name_W83627DHG[] = "W83627DHG"; static const char __initdata sio_name_W83627DHG_P[] = "W83627DHG-P"; static const char __initdata sio_name_W83667HG[] = "W83667HG"; + static const char __initdata sio_name_W83667HG_B[] = "W83667HG-B"; u16 val; const char *sio_name; @@ -1588,6 +1661,10 @@ static int __init w83627ehf_find(int sioaddr, unsigned short *addr, sio_data->kind = w83667hg; sio_name = sio_name_W83667HG; break; + case SIO_W83667HG_B_ID: + sio_data->kind = w83667hg_b; + sio_name = sio_name_W83667HG_B; + break; default: if (val != 0xffff) pr_debug(DRVNAME ": unsupported chip ID: 0x%04x\n", |