From 3b02d332b6f15cc8f7b6a04757c86034669600e0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 Jan 2009 16:37:31 +0100 Subject: hwmon: (f71882fg) Add documentation Add some documentation about the f71882fg driver, and update the Kconfig documentation to report the new supported models. Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- drivers/hwmon/Kconfig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/hwmon/Kconfig') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c709e821f04..cc611e4b789 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -284,11 +284,12 @@ config SENSORS_F71805F will be called f71805f. config SENSORS_F71882FG - tristate "Fintek F71882FG and F71883FG" + tristate "Fintek F71862FG, F71882FG and F8000" depends on EXPERIMENTAL help If you say yes here you get support for hardware monitoring - features of the Fintek F71882FG and F71883FG Super-I/O chips. + features of the Fintek F71882FG/F71883FG, F71862FG/71863FG + and F8000 Super-I/O chips. This driver can also be built as a module. If so, the module will be called f71882fg. -- cgit v1.2.3-70-g09d2 From 6e34b187bc216fc632769fb8b906d3a29ccd8f14 Mon Sep 17 00:00:00 2001 From: Ira Snyder Date: Wed, 7 Jan 2009 16:37:32 +0100 Subject: hwmon: Add LTC4245 driver Add Linux support for the Linear Technology LTC4245 Multiple Supply Hot Swap controller I2C monitoring interface. Signed-off-by: Ira W. Snyder Acked-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/ltc4245 | 81 +++++++ drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/ltc4245.c | 567 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 660 insertions(+) create mode 100644 Documentation/hwmon/ltc4245 create mode 100644 drivers/hwmon/ltc4245.c (limited to 'drivers/hwmon/Kconfig') diff --git a/Documentation/hwmon/ltc4245 b/Documentation/hwmon/ltc4245 new file mode 100644 index 00000000000..bae7a3adc5d --- /dev/null +++ b/Documentation/hwmon/ltc4245 @@ -0,0 +1,81 @@ +Kernel driver ltc4245 +===================== + +Supported chips: + * Linear Technology LTC4245 + Prefix: 'ltc4245' + Addresses scanned: 0x20-0x3f + Datasheet: + http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517 + +Author: Ira W. Snyder + + +Description +----------- + +The LTC4245 controller allows a board to be safely inserted and removed +from a live backplane in multiple supply systems such as CompactPCI and +PCI Express. + + +Usage Notes +----------- + +This driver does not probe for LTC4245 devices, due to the fact that some +of the possible addresses are unfriendly to probing. You will need to use +the "force" parameter to tell the driver where to find the device. + +Example: the following will load the driver for an LTC4245 at address 0x23 +on I2C bus #1: +$ modprobe ltc4245 force=1,0x23 + + +Sysfs entries +------------- + +The LTC4245 has built-in limits for over and under current warnings. This +makes it very likely that the reference circuit will be used. + +This driver uses the values in the datasheet to change the register values +into the values specified in the sysfs-interface document. The current readings +rely on the sense resistors listed in Table 2: "Sense Resistor Values". + +in1_input 12v input voltage (mV) +in2_input 5v input voltage (mV) +in3_input 3v input voltage (mV) +in4_input Vee (-12v) input voltage (mV) + +in1_min_alarm 12v input undervoltage alarm +in2_min_alarm 5v input undervoltage alarm +in3_min_alarm 3v input undervoltage alarm +in4_min_alarm Vee (-12v) input undervoltage alarm + +curr1_input 12v current (mA) +curr2_input 5v current (mA) +curr3_input 3v current (mA) +curr4_input Vee (-12v) current (mA) + +curr1_max_alarm 12v overcurrent alarm +curr2_max_alarm 5v overcurrent alarm +curr3_max_alarm 3v overcurrent alarm +curr4_max_alarm Vee (-12v) overcurrent alarm + +in5_input 12v output voltage (mV) +in6_input 5v output voltage (mV) +in7_input 3v output voltage (mV) +in8_input Vee (-12v) output voltage (mV) + +in5_min_alarm 12v output undervoltage alarm +in6_min_alarm 5v output undervoltage alarm +in7_min_alarm 3v output undervoltage alarm +in8_min_alarm Vee (-12v) output undervoltage alarm + +in9_input GPIO #1 voltage data +in10_input GPIO #2 voltage data +in11_input GPIO #3 voltage data + +power1_input 12v power usage (mW) +power2_input 5v power usage (mW) +power3_input 3v power usage (mW) +power4_input Vee (-12v) power usage (mW) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index cc611e4b789..1ef1205b4e8 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -549,6 +549,17 @@ config SENSORS_LM93 This driver can also be built as a module. If so, the module will be called lm93. +config SENSORS_LTC4245 + tristate "Linear Technology LTC4245" + depends on I2C && EXPERIMENTAL + default n + help + If you say yes here you get support for Linear Technology LTC4245 + Multiple Supply Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4245. + config SENSORS_MAX1111 tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip" depends on SPI_MASTER diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 58fc5be5355..8fd124eff64 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c new file mode 100644 index 00000000000..034b2c51584 --- /dev/null +++ b/drivers/hwmon/ltc4245.c @@ -0,0 +1,567 @@ +/* + * Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller + * + * Copyright (C) 2008 Ira W. Snyder + * + * 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 is based on the ds1621 and ina209 drivers. + * + * Datasheet: + * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Valid addresses are 0x20 - 0x3f + * + * For now, we do not probe, since some of these addresses + * are known to be unfriendly to probing */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(ltc4245); + +/* Here are names of the chip's registers (a.k.a. commands) */ +enum ltc4245_cmd { + LTC4245_STATUS = 0x00, /* readonly */ + LTC4245_ALERT = 0x01, + LTC4245_CONTROL = 0x02, + LTC4245_ON = 0x03, + LTC4245_FAULT1 = 0x04, + LTC4245_FAULT2 = 0x05, + LTC4245_GPIO = 0x06, + LTC4245_ADCADR = 0x07, + + LTC4245_12VIN = 0x10, + LTC4245_12VSENSE = 0x11, + LTC4245_12VOUT = 0x12, + LTC4245_5VIN = 0x13, + LTC4245_5VSENSE = 0x14, + LTC4245_5VOUT = 0x15, + LTC4245_3VIN = 0x16, + LTC4245_3VSENSE = 0x17, + LTC4245_3VOUT = 0x18, + LTC4245_VEEIN = 0x19, + LTC4245_VEESENSE = 0x1a, + LTC4245_VEEOUT = 0x1b, + LTC4245_GPIOADC1 = 0x1c, + LTC4245_GPIOADC2 = 0x1d, + LTC4245_GPIOADC3 = 0x1e, +}; + +struct ltc4245_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Control registers */ + u8 cregs[0x08]; + + /* Voltage registers */ + u8 vregs[0x0f]; +}; + +static struct ltc4245_data *ltc4245_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4245_data *data = i2c_get_clientdata(client); + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + + dev_dbg(&client->dev, "Starting ltc4245 update\n"); + + /* Read control registers -- 0x00 to 0x07 */ + for (i = 0; i < ARRAY_SIZE(data->cregs); i++) { + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) + data->cregs[i] = 0; + else + data->cregs[i] = val; + } + + /* Read voltage registers -- 0x10 to 0x1f */ + for (i = 0; i < ARRAY_SIZE(data->vregs); i++) { + val = i2c_smbus_read_byte_data(client, i+0x10); + if (unlikely(val < 0)) + data->vregs[i] = 0; + else + data->vregs[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Return the voltage from the given register in millivolts */ +static int ltc4245_get_voltage(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + u32 voltage = 0; + + switch (reg) { + case LTC4245_12VIN: + case LTC4245_12VOUT: + voltage = regval * 55; + break; + case LTC4245_5VIN: + case LTC4245_5VOUT: + voltage = regval * 22; + break; + case LTC4245_3VIN: + case LTC4245_3VOUT: + voltage = regval * 15; + break; + case LTC4245_VEEIN: + case LTC4245_VEEOUT: + voltage = regval * -55; + break; + case LTC4245_GPIOADC1: + case LTC4245_GPIOADC2: + case LTC4245_GPIOADC3: + voltage = regval * 10; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return voltage; +} + +/* Return the current in the given sense register in milliAmperes */ +static unsigned int ltc4245_get_current(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + unsigned int voltage; + unsigned int curr; + + /* The strange looking conversions that follow are fixed-point + * math, since we cannot do floating point in the kernel. + * + * Step 1: convert sense register to microVolts + * Step 2: convert voltage to milliAmperes + * + * If you play around with the V=IR equation, you come up with + * the following: X uV / Y mOhm == Z mA + * + * With the resistors that are fractions of a milliOhm, we multiply + * the voltage and resistance by 10, to shift the decimal point. + * Now we can use the normal division operator again. + */ + + switch (reg) { + case LTC4245_12VSENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 50; /* sense resistor 50 mOhm */ + break; + case LTC4245_5VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 35; /* sense resistor 3.5 mOhm */ + break; + case LTC4245_3VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 25; /* sense resistor 2.5 mOhm */ + break; + case LTC4245_VEESENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 100; /* sense resistor 100 mOhm */ + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + curr = 0; + break; + } + + return curr; +} + +static ssize_t ltc4245_show_voltage(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const int voltage = ltc4245_get_voltage(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage); +} + +static ssize_t ltc4245_show_current(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const unsigned int curr = ltc4245_get_current(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%u\n", curr); +} + +static ssize_t ltc4245_show_power(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const unsigned int curr = ltc4245_get_current(dev, attr->index); + const int output_voltage = ltc4245_get_voltage(dev, attr->index+1); + + /* current in mA * voltage in mV == power in uW */ + const unsigned int power = abs(output_voltage * curr); + + return snprintf(buf, PAGE_SIZE, "%u\n", power); +} + +static ssize_t ltc4245_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 reg = data->cregs[attr->index]; + const u32 mask = attr->nr; + + return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); +} + +/* 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 LTC4245_VOLTAGE(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_voltage, NULL, ltc4245_cmd_idx) + +#define LTC4245_CURRENT(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_current, NULL, ltc4245_cmd_idx) + +#define LTC4245_POWER(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_power, NULL, ltc4245_cmd_idx) + +#define LTC4245_ALARM(name, mask, reg) \ + static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ + ltc4245_show_alarm, NULL, (mask), reg) + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +LTC4245_VOLTAGE(in1_input, LTC4245_12VIN); +LTC4245_VOLTAGE(in2_input, LTC4245_5VIN); +LTC4245_VOLTAGE(in3_input, LTC4245_3VIN); +LTC4245_VOLTAGE(in4_input, LTC4245_VEEIN); + +/* Input undervoltage alarms */ +LTC4245_ALARM(in1_min_alarm, (1 << 0), LTC4245_FAULT1); +LTC4245_ALARM(in2_min_alarm, (1 << 1), LTC4245_FAULT1); +LTC4245_ALARM(in3_min_alarm, (1 << 2), LTC4245_FAULT1); +LTC4245_ALARM(in4_min_alarm, (1 << 3), LTC4245_FAULT1); + +/* Currents (via sense resistor) */ +LTC4245_CURRENT(curr1_input, LTC4245_12VSENSE); +LTC4245_CURRENT(curr2_input, LTC4245_5VSENSE); +LTC4245_CURRENT(curr3_input, LTC4245_3VSENSE); +LTC4245_CURRENT(curr4_input, LTC4245_VEESENSE); + +/* Overcurrent alarms */ +LTC4245_ALARM(curr1_max_alarm, (1 << 4), LTC4245_FAULT1); +LTC4245_ALARM(curr2_max_alarm, (1 << 5), LTC4245_FAULT1); +LTC4245_ALARM(curr3_max_alarm, (1 << 6), LTC4245_FAULT1); +LTC4245_ALARM(curr4_max_alarm, (1 << 7), LTC4245_FAULT1); + +/* Output voltages */ +LTC4245_VOLTAGE(in5_input, LTC4245_12VOUT); +LTC4245_VOLTAGE(in6_input, LTC4245_5VOUT); +LTC4245_VOLTAGE(in7_input, LTC4245_3VOUT); +LTC4245_VOLTAGE(in8_input, LTC4245_VEEOUT); + +/* Power Bad alarms */ +LTC4245_ALARM(in5_min_alarm, (1 << 0), LTC4245_FAULT2); +LTC4245_ALARM(in6_min_alarm, (1 << 1), LTC4245_FAULT2); +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_GPIOADC1); +LTC4245_VOLTAGE(in10_input, LTC4245_GPIOADC2); +LTC4245_VOLTAGE(in11_input, LTC4245_GPIOADC3); + +/* Power Consumption (virtual) */ +LTC4245_POWER(power1_input, LTC4245_12VSENSE); +LTC4245_POWER(power2_input, LTC4245_5VSENSE); +LTC4245_POWER(power3_input, LTC4245_3VSENSE); +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[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + &sensor_dev_attr_in3_min_alarm.dev_attr.attr, + &sensor_dev_attr_in4_min_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_curr3_input.dev_attr.attr, + &sensor_dev_attr_curr4_input.dev_attr.attr, + + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr3_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr4_max_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + + &sensor_dev_attr_in5_min_alarm.dev_attr.attr, + &sensor_dev_attr_in6_min_alarm.dev_attr.attr, + &sensor_dev_attr_in7_min_alarm.dev_attr.attr, + &sensor_dev_attr_in8_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power2_input.dev_attr.attr, + &sensor_dev_attr_power3_input.dev_attr.attr, + &sensor_dev_attr_power4_input.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ltc4245_group = { + .attrs = ltc4245_attributes, +}; + +static int ltc4245_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ltc4245_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out_kzalloc; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the LTC4245 chip */ + /* TODO */ + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, <c4245_group); + if (ret) + goto out_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, <c4245_group); +out_sysfs_create_group: + kfree(data); +out_kzalloc: + return ret; +} + +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); + + kfree(data); + + return 0; +} + +/* Check that some bits in a control register appear at all possible + * locations without changing value + * + * @client: the i2c client to use + * @reg: the register to read + * @bits: the bits to check (0xff checks all bits, + * 0x03 checks only the last two bits) + * + * return -ERRNO if the register read failed + * return -ENODEV if the register value doesn't stay constant at all + * possible addresses + * + * return 0 for success + */ +static int ltc4245_check_control_reg(struct i2c_client *client, u8 reg, u8 bits) +{ + int i; + s32 v, voff1, voff2; + + /* Read register and check for error */ + v = i2c_smbus_read_byte_data(client, reg); + if (v < 0) + return v; + + v &= bits; + + for (i = 0x00; i < 0xff; i += 0x20) { + + voff1 = i2c_smbus_read_byte_data(client, reg + i); + if (voff1 < 0) + return voff1; + + voff2 = i2c_smbus_read_byte_data(client, reg + i + 0x08); + if (voff2 < 0) + return voff2; + + voff1 &= bits; + voff2 &= bits; + + if (v != voff1 || v != voff2) + return -ENODEV; + } + + return 0; +} + +static int ltc4245_detect(struct i2c_client *client, + int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (kind < 0) { /* probed detection - check the chip type */ + s32 v; /* 8 bits from the chip, or -ERRNO */ + + /* Chip registers 0x00-0x07 are control registers + * Chip registers 0x10-0x1f are data registers + * + * Address bits b7-b5 are ignored. This makes the chip "repeat" + * in steps of 0x20. Any control registers should appear with + * the same values across all duplicated addresses. + * + * Register 0x02 bit b2 is reserved, expect 0 + * Register 0x07 bits b7 to b4 are reserved, expect 0 + * + * Registers 0x01, 0x02 are control registers and should not + * change on their own. + * + * Register 0x06 bits b6 and b7 are control bits, and should + * not change on their own. + * + * Register 0x07 bits b3 to b0 are control bits, and should + * not change on their own. + */ + + /* read register 0x02 reserved bit, expect 0 */ + v = i2c_smbus_read_byte_data(client, LTC4245_CONTROL); + if (v < 0 || (v & 0x04) != 0) + return -ENODEV; + + /* read register 0x07 reserved bits, expect 0 */ + v = i2c_smbus_read_byte_data(client, LTC4245_ADCADR); + if (v < 0 || (v & 0xf0) != 0) + return -ENODEV; + + /* check that the alert register appears at all locations */ + if (ltc4245_check_control_reg(client, LTC4245_ALERT, 0xff)) + return -ENODEV; + + /* check that the control register appears at all locations */ + if (ltc4245_check_control_reg(client, LTC4245_CONTROL, 0xff)) + return -ENODEV; + + /* check that register 0x06 bits b6 and b7 stay constant */ + if (ltc4245_check_control_reg(client, LTC4245_GPIO, 0xc0)) + return -ENODEV; + + /* check that register 0x07 bits b3-b0 stay constant */ + if (ltc4245_check_control_reg(client, LTC4245_ADCADR, 0x0f)) + return -ENODEV; + } + + strlcpy(info->type, "ltc4245", I2C_NAME_SIZE); + dev_info(&adapter->dev, "ltc4245 %s at address 0x%02x\n", + kind < 0 ? "probed" : "forced", + client->addr); + + return 0; +} + +static const struct i2c_device_id ltc4245_id[] = { + { "ltc4245", ltc4245 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4245_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4245_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ltc4245", + }, + .probe = ltc4245_probe, + .remove = ltc4245_remove, + .id_table = ltc4245_id, + .detect = ltc4245_detect, + .address_data = &addr_data, +}; + +static int __init ltc4245_init(void) +{ + return i2c_add_driver(<c4245_driver); +} + +static void __exit ltc4245_exit(void) +{ + i2c_del_driver(<c4245_driver); +} + +MODULE_AUTHOR("Ira W. Snyder "); +MODULE_DESCRIPTION("LTC4245 driver"); +MODULE_LICENSE("GPL"); + +module_init(ltc4245_init); +module_exit(ltc4245_exit); -- cgit v1.2.3-70-g09d2 From b4da93e4b0ffc261c3530fe938aefd52854aa84c Mon Sep 17 00:00:00 2001 From: Jean-Marc Spaggiari Date: Wed, 7 Jan 2009 16:37:32 +0100 Subject: hwmon: (it87) Add support for the ITE IT8720F Allow it87.c to handle IT8720 chipset like IT8718 in order to retrieve voltage, temperatures and fans speed from sensors tools. Also updating the related documentation. Signed-off-by: Jean-Marc Spaggiari Signed-off-by: Jean Delvare --- Documentation/hwmon/it87 | 20 ++++++++++++-------- drivers/hwmon/Kconfig | 3 ++- drivers/hwmon/it87.c | 30 +++++++++++++++++++++--------- 3 files changed, 35 insertions(+), 18 deletions(-) (limited to 'drivers/hwmon/Kconfig') diff --git a/Documentation/hwmon/it87 b/Documentation/hwmon/it87 index 042c0415140..659315d98e0 100644 --- a/Documentation/hwmon/it87 +++ b/Documentation/hwmon/it87 @@ -26,6 +26,10 @@ Supported chips: Datasheet: Publicly available at the ITE website http://www.ite.com.tw/product_info/file/pc/IT8718F_V0.2.zip http://www.ite.com.tw/product_info/file/pc/IT8718F_V0%203_(for%20C%20version).zip + * IT8720F + Prefix: 'it8720' + Addresses scanned: from Super I/O config space (8 I/O ports) + Datasheet: Not yet publicly available. * SiS950 [clone of IT8705F] Prefix: 'it87' Addresses scanned: from Super I/O config space (8 I/O ports) @@ -71,7 +75,7 @@ Description ----------- This driver implements support for the IT8705F, IT8712F, IT8716F, -IT8718F, IT8726F and SiS950 chips. +IT8718F, IT8720F, IT8726F and SiS950 chips. These chips are 'Super I/O chips', supporting floppy disks, infrared ports, joysticks and other miscellaneous stuff. For hardware monitoring, they @@ -84,19 +88,19 @@ the IT8716F and late IT8712F have 6. They are shared with other functions though, so the functionality may not be available on a given system. The driver dumbly assume it is there. -The IT8718F also features VID inputs (up to 8 pins) but the value is -stored in the Super-I/O configuration space. Due to technical limitations, +The IT8718F and IT8720F also features VID inputs (up to 8 pins) but the value +is stored in the Super-I/O configuration space. Due to technical limitations, this value can currently only be read once at initialization time, so the driver won't notice and report changes in the VID value. The two upper VID bits share their pins with voltage inputs (in5 and in6) so you can't have both on a given board. -The IT8716F, IT8718F and later IT8712F revisions have support for +The IT8716F, IT8718F, IT8720F and later IT8712F revisions have support for 2 additional fans. The additional fans are supported by the driver. -The IT8716F and IT8718F, and late IT8712F and IT8705F also have optional -16-bit tachometer counters for fans 1 to 3. This is better (no more fan -clock divider mess) but not compatible with the older chips and +The IT8716F, IT8718F and IT8720F, and late IT8712F and IT8705F also have +optional 16-bit tachometer counters for fans 1 to 3. This is better (no more +fan clock divider mess) but not compatible with the older chips and revisions. The 16-bit tachometer mode is enabled by the driver when one of the above chips is detected. @@ -122,7 +126,7 @@ zero'; this is important for negative voltage measurements. All voltage inputs can measure voltages between 0 and 4.08 volts, with a resolution of 0.016 volt. The battery voltage in8 does not have limit registers. -The VID lines (IT8712F/IT8716F/IT8718F) encode the core voltage value: +The VID lines (IT8712F/IT8716F/IT8718F/IT8720F) encode the core voltage value: the voltage level your processor should work with. This is hardcoded by the mainboard and/or processor itself. It is a value in volts. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 1ef1205b4e8..aba01b4ceca 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -400,7 +400,8 @@ config SENSORS_IT87 select HWMON_VID help If you say yes here you get support for ITE IT8705F, IT8712F, - IT8716F, IT8718F and IT8726F sensor chips, and the SiS960 clone. + IT8716F, IT8718F, IT8720F and IT8726F sensor chips, and the + SiS960 clone. This driver can also be built as a module. If so, the module will be called it87. diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index b74c95735f9..0e0d692f0c9 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -14,6 +14,7 @@ IT8712F Super I/O chip w/LPC interface IT8716F Super I/O chip w/LPC interface IT8718F Super I/O chip w/LPC interface + IT8720F Super I/O chip w/LPC interface IT8726F Super I/O chip w/LPC interface Sis950 A clone of the IT8705F @@ -52,7 +53,7 @@ #define DRVNAME "it87" -enum chips { it87, it8712, it8716, it8718 }; +enum chips { it87, it8712, it8716, it8718, it8720 }; static unsigned short force_id; module_param(force_id, ushort, 0); @@ -64,7 +65,10 @@ static struct platform_device *pdev; #define DEV 0x07 /* Register: Logical device select */ #define VAL 0x2f /* The value to read/write */ #define PME 0x04 /* The device with the fan registers in it */ -#define GPIO 0x07 /* The device with the IT8718F VID value in it */ + +/* The device with the IT8718F/IT8720F VID value in it */ +#define GPIO 0x07 + #define DEVID 0x20 /* Register: Device ID */ #define DEVREV 0x22 /* Register: Device Revision */ @@ -113,6 +117,7 @@ superio_exit(void) #define IT8705F_DEVID 0x8705 #define IT8716F_DEVID 0x8716 #define IT8718F_DEVID 0x8718 +#define IT8720F_DEVID 0x8720 #define IT8726F_DEVID 0x8726 #define IT87_ACT_REG 0x30 #define IT87_BASE_REG 0x60 @@ -150,8 +155,8 @@ static int fix_pwm_polarity; #define IT87_REG_ALARM2 0x02 #define IT87_REG_ALARM3 0x03 -/* The IT8718F has the VID value in a different register, in Super-I/O - configuration space. */ +/* The IT8718F and IT8720F have the VID value in a different register, in + Super-I/O configuration space. */ #define IT87_REG_VID 0x0a /* The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b for fan divisors. Later IT8712F revisions must use 16-bit tachometer @@ -282,7 +287,8 @@ static inline int has_16bit_fans(const struct it87_data *data) return (data->type == it87 && data->revision >= 0x03) || (data->type == it8712 && data->revision >= 0x08) || data->type == it8716 - || data->type == it8718; + || data->type == it8718 + || data->type == it8720; } static int it87_probe(struct platform_device *pdev); @@ -992,6 +998,9 @@ static int __init it87_find(unsigned short *address, case IT8718F_DEVID: sio_data->type = it8718; break; + case IT8720F_DEVID: + sio_data->type = it8720; + break; case 0xffff: /* No device at all */ goto exit; default: @@ -1022,7 +1031,8 @@ static int __init it87_find(unsigned short *address, int reg; superio_select(GPIO); - if (chip_type == it8718) + if ((chip_type == it8718) || + (chip_type == it8720)) sio_data->vid_value = superio_inb(IT87_SIO_VID_REG); reg = superio_inb(IT87_SIO_PINX2_REG); @@ -1068,6 +1078,7 @@ static int __devinit it87_probe(struct platform_device *pdev) "it8712", "it8716", "it8718", + "it8720", }; res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -1226,7 +1237,7 @@ static int __devinit it87_probe(struct platform_device *pdev) } if (data->type == it8712 || data->type == it8716 - || data->type == it8718) { + || data->type == it8718 || data->type == it8720) { data->vrm = vid_which_vrm(); /* VID reading from Super-I/O config space if available */ data->vid = sio_data->vid_value; @@ -1513,7 +1524,8 @@ static struct it87_data *it87_update_device(struct device *dev) data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); /* The 8705 does not have VID capability. - The 8718 does not use IT87_REG_VID for the same purpose. */ + The 8718 and the 8720 don't use IT87_REG_VID for the + same purpose. */ if (data->type == it8712 || data->type == it8716) { data->vid = it87_read_value(data, IT87_REG_VID); /* The older IT8712F revisions had only 5 VID pins, @@ -1608,7 +1620,7 @@ static void __exit sm_it87_exit(void) MODULE_AUTHOR("Chris Gauthron, " "Jean Delvare "); -MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8726F, SiS950 driver"); +MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8720F/8726F, SiS950 driver"); module_param(update_vbat, bool, 0); MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); module_param(fix_pwm_polarity, bool, 0); -- cgit v1.2.3-70-g09d2 From 97950c3d423e474ef887749b238ee67731b532fe Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 Jan 2009 16:37:33 +0100 Subject: hwmon: (fschmd) Add watchdog support This patch adds support for the watchdog part found in _all_ supported FSC sensor chips. Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- drivers/hwmon/Kconfig | 5 +- drivers/hwmon/fschmd.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 386 insertions(+), 20 deletions(-) (limited to 'drivers/hwmon/Kconfig') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index aba01b4ceca..91975148f4b 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -329,10 +329,11 @@ config SENSORS_FSCHMD depends on X86 && I2C && EXPERIMENTAL help If you say yes here you get support for various Fujitsu Siemens - Computers sensor chips. + Computers sensor chips, including support for the integrated + watchdog. This is a new merged driver for FSC sensor chips which is intended - as a replacment for the fscpos, fscscy and fscher drivers and adds + as a replacement for the fscpos, fscscy and fscher drivers and adds support for several other FCS sensor chips. This driver can also be built as a module. If so, the module diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c index c188b713502..8bb1a44a1de 100644 --- a/drivers/hwmon/fschmd.c +++ b/drivers/hwmon/fschmd.c @@ -42,11 +42,20 @@ #include #include #include +#include +#include +#include +#include +#include /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; /* Insmod parameters */ +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); /* @@ -65,11 +74,18 @@ I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); #define FSCHMD_CONTROL_ALERT_LED 0x01 -/* watchdog (support to be implemented) */ +/* watchdog */ #define FSCHMD_REG_WDOG_PRESET 0x28 #define FSCHMD_REG_WDOG_STATE 0x23 #define FSCHMD_REG_WDOG_CONTROL 0x21 +#define FSCHMD_WDOG_CONTROL_TRIGGER 0x10 +#define FSCHMD_WDOG_CONTROL_STARTED 0x10 /* the same as trigger */ +#define FSCHMD_WDOG_CONTROL_STOP 0x20 +#define FSCHMD_WDOG_CONTROL_RESOLUTION 0x40 + +#define FSCHMD_WDOG_STATE_CARDRESET 0x02 + /* voltages, weird order is to keep the same order as the old drivers */ static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; @@ -206,14 +222,26 @@ static struct i2c_driver fschmd_driver = { */ struct fschmd_data { + struct i2c_client *client; struct device *hwmon_dev; struct mutex update_lock; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; int kind; + unsigned long watchdog_is_open; + char watchdog_expect_close; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ char valid; /* zero until following fields are valid */ unsigned long last_updated; /* in jiffies */ /* register values */ + u8 revision; /* chip revision */ u8 global_control; /* global control register */ + u8 watchdog_control; /* watchdog control register */ + u8 watchdog_state; /* watchdog status register */ + u8 watchdog_preset; /* watchdog counter preset on trigger val */ u8 volt[3]; /* 12, 5, battery voltage */ u8 temp_act[5]; /* temperature */ u8 temp_status[5]; /* status of sensor */ @@ -225,11 +253,28 @@ struct fschmd_data { }; /* Global variables to hold information read from special DMI tables, which are - available on FSC machines with an fscher or later chip. */ + available on FSC machines with an fscher or later chip. There is no need to + protect these with a lock as they are only modified from our attach function + which always gets called with the i2c-core lock held and never accessed + before the attach function is done with them. */ static int dmi_mult[3] = { 490, 200, 100 }; static int dmi_offset[3] = { 0, 0, 0 }; static int dmi_vref = -1; +/* Somewhat ugly :( global data pointer list with all fschmd devices, so that + we can find our device data as when using misc_register there is no other + method to get to ones device data from the open fop. */ +static LIST_HEAD(watchdog_data_list); +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + +/* Release our data struct when we're detached from the i2c client *and* all + references to our watchdog device are released */ +static void fschmd_release_resources(struct kref *ref) +{ + struct fschmd_data *data = container_of(ref, struct fschmd_data, kref); + kfree(data); +} /* * Sysfs attr show / store functions @@ -548,7 +593,265 @@ static struct sensor_device_attribute fschmd_fan_attr[] = { /* - * Real code + * Watchdog routines + */ + +static int watchdog_set_timeout(struct fschmd_data *data, int timeout) +{ + int ret, resolution; + int kind = data->kind + 1; /* 0-x array index -> 1-x module param */ + + /* 2 second or 60 second resolution? */ + if (timeout <= 510 || kind == fscpos || kind == fscscy) + resolution = 2; + else + resolution = 60; + + if (timeout < resolution || timeout > (resolution * 255)) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + if (resolution == 2) + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_RESOLUTION; + else + data->watchdog_control |= FSCHMD_WDOG_CONTROL_RESOLUTION; + + data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + + /* Write new timeout value */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_PRESET, + data->watchdog_preset); + /* Write new control register, do not trigger! */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER); + + ret = data->watchdog_preset * resolution; + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct fschmd_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + if (data->watchdog_control & FSCHMD_WDOG_CONTROL_RESOLUTION) + timeout = data->watchdog_preset * 60; + else + timeout = data->watchdog_preset * 2; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_trigger(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control |= FSCHMD_WDOG_CONTROL_TRIGGER; + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_stop(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED; + /* Don't store the stop flag in our watchdog control register copy, as + its a write only bit (read always returns 0) */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct fschmd_data *pos, *data = NULL; + + /* We get called from drivers/char/misc.c with misc_mtx hold, and we + call misc_register() from fschmd_probe() with watchdog_data_mutex + hold, as misc_register() takes the misc_mtx lock, this is a possible + deadlock, so we use mutex_trylock here. */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + /* Note we can never not have found data, so we don't check for this */ + kref_get(&data->kref); + mutex_unlock(&watchdog_data_mutex); + + if (test_and_set_bit(0, &data->watchdog_is_open)) + return -EBUSY; + + /* Start the watchdog */ + watchdog_trigger(data); + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int watchdog_release(struct inode *inode, struct file *filp) +{ + struct fschmd_data *data = filp->private_data; + + if (data->watchdog_expect_close) { + watchdog_stop(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + dev_crit(&data->client->dev, + "unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + size_t ret; + struct fschmd_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret < 0) + return ret; + } + return count; +} + +static int watchdog_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_CARDRESET, + .identity = "FSC watchdog" + }; + int i, ret = 0; + struct fschmd_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ident.firmware_version = data->revision; + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET) + ret = put_user(WDIOF_CARDRESET, (int __user *)arg); + else + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + i = watchdog_get_timeout(data); + ret = put_user(i, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, i); + if (ret > 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (i & WDIOS_DISABLECARD) + ret = watchdog_stop(data); + else if (i & WDIOS_ENABLECARD) + ret = watchdog_trigger(data); + else + ret = -EINVAL; + + break; + default: + ret = -ENOTTY; + } + + return ret; +} + +static struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .ioctl = watchdog_ioctl, +}; + + +/* + * Detect, register, unregister and update device functions */ /* DMI decode routine to read voltage scaling factors from special DMI tables, @@ -658,9 +961,9 @@ static int fschmd_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct fschmd_data *data; - u8 revision; const char * const names[5] = { "Poseidon", "Hermes", "Scylla", "Heracles", "Heimdall" }; + const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; int i, err; enum chips kind = id->driver_data; @@ -670,6 +973,13 @@ static int fschmd_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + /* Store client pointer in our data struct for watchdog usage + (where the client is found through a data ptr instead of the + otherway around) */ + data->client = client; if (kind == fscpos) { /* The Poseidon has hardwired temp limits, fill these @@ -690,6 +1000,17 @@ static int fschmd_probe(struct i2c_client *client, } } + /* Read in some never changing registers */ + data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); + data->global_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_CONTROL); + data->watchdog_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_CONTROL); + data->watchdog_state = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_STATE); + data->watchdog_preset = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_PRESET); + /* i2c kind goes from 1-5, we want from 0-4 to address arrays */ data->kind = kind - 1; @@ -732,9 +1053,43 @@ static int fschmd_probe(struct i2c_client *client, goto exit_detach; } - revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); + /* We take the data_mutex lock early so that watchdog_open() cannot + run when misc_register() has completed, but we've not yet added + our data to the watchdog_data_list (and set the default timeout) */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) { + data->watchdog_miscdev.minor = 0; + dev_err(&client->dev, + "Registering watchdog chardev: %d\n", err); + break; + } + + list_add(&data->list, &watchdog_data_list); + watchdog_set_timeout(data, 60); + dev_info(&client->dev, + "Registered watchdog chardev major 10, minor: %d\n", + watchdog_minors[i]); + break; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + data->watchdog_miscdev.minor = 0; + dev_warn(&client->dev, "Couldn't register watchdog chardev " + "(due to no free minor)\n"); + } + mutex_unlock(&watchdog_data_mutex); + dev_info(&client->dev, "Detected FSC %s chip, revision: %d\n", - names[data->kind], (int) revision); + names[data->kind], (int) data->revision); return 0; @@ -748,6 +1103,24 @@ static int fschmd_remove(struct i2c_client *client) struct fschmd_data *data = i2c_get_clientdata(client); int i; + /* Unregister the watchdog (if registered) */ + if (data->watchdog_miscdev.minor) { + misc_deregister(&data->watchdog_miscdev); + if (data->watchdog_is_open) { + dev_warn(&client->dev, + "i2c client detached with watchdog open! " + "Stopping watchdog.\n"); + watchdog_stop(data); + } + mutex_lock(&watchdog_data_mutex); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + /* Tell the watchdog code the client is gone */ + mutex_lock(&data->watchdog_lock); + data->client = NULL; + mutex_unlock(&data->watchdog_lock); + } + /* Check if registered in case we're called from fschmd_detect to cleanup after an error */ if (data->hwmon_dev) @@ -762,7 +1135,10 @@ static int fschmd_remove(struct i2c_client *client) device_remove_file(&client->dev, &fschmd_fan_attr[i].dev_attr); - kfree(data); + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + return 0; } @@ -824,17 +1200,6 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) data->volt[i] = i2c_smbus_read_byte_data(client, FSCHMD_REG_VOLT[i]); - data->global_control = i2c_smbus_read_byte_data(client, - FSCHMD_REG_CONTROL); - - /* To be implemented in the future - data->watchdog[0] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_PRESET); - data->watchdog[1] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_STATE); - data->watchdog[2] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_CONTROL); */ - data->last_updated = jiffies; data->valid = 1; } -- cgit v1.2.3-70-g09d2 From 0589c2de643ef71a684ba6d219532f9e2a3e554b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 Jan 2009 16:37:33 +0100 Subject: hwmon: Deprecate the fscher and fscpos drivers Now that the new merged fschmd driver has gained support for the watchdog integrated into these IC's, there is no more reason to keep the old fscher and fscpos drivers around, so mark them as deprecated. Signed-off-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/feature-removal-schedule.txt | 8 ++++++++ drivers/hwmon/Kconfig | 20 ++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'drivers/hwmon/Kconfig') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 2193be53e77..5ddbe350487 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -318,6 +318,14 @@ Who: Jean Delvare --------------------------- +What: fscher and fscpos drivers +When: June 2009 +Why: Deprecated by the new fschmd driver. +Who: Hans de Goede + Jean Delvare + +--------------------------- + What: SELinux "compat_net" functionality When: 2.6.30 at the earliest Why: In 2.6.18 the Secmark concept was introduced to replace the "compat_net" diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 91975148f4b..3c34fb5e419 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -305,9 +305,13 @@ config SENSORS_F75375S will be called f75375s. config SENSORS_FSCHER - tristate "FSC Hermes" + tristate "FSC Hermes (DEPRECATED)" depends on X86 && I2C help + This driver is DEPRECATED please use the new merged fschmd + ("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver + instead. + If you say yes here you get support for Fujitsu Siemens Computers Hermes sensor chips. @@ -315,9 +319,13 @@ config SENSORS_FSCHER will be called fscher. config SENSORS_FSCPOS - tristate "FSC Poseidon" + tristate "FSC Poseidon (DEPRECATED)" depends on X86 && I2C help + This driver is DEPRECATED please use the new merged fschmd + ("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver + instead. + If you say yes here you get support for Fujitsu Siemens Computers Poseidon sensor chips. @@ -326,15 +334,15 @@ config SENSORS_FSCPOS config SENSORS_FSCHMD tristate "FSC Poseidon, Scylla, Hermes, Heimdall and Heracles" - depends on X86 && I2C && EXPERIMENTAL + depends on X86 && I2C help If you say yes here you get support for various Fujitsu Siemens Computers sensor chips, including support for the integrated watchdog. - This is a new merged driver for FSC sensor chips which is intended - as a replacement for the fscpos, fscscy and fscher drivers and adds - support for several other FCS sensor chips. + This is a merged driver for FSC sensor chips replacing the fscpos, + fscscy and fscher drivers and adding support for several other FSC + sensor chips. This driver can also be built as a module. If so, the module will be called fschmd. -- cgit v1.2.3-70-g09d2 From c8ac32e4711639c81e5f4d4cd78c8f21675a2bae Mon Sep 17 00:00:00 2001 From: Manuel Lauss Date: Wed, 7 Jan 2009 16:37:34 +0100 Subject: hwmon: (lm70) Add TI TMP121 support The Texas Instruments TMP121 is a SPI temperature sensor very similar to the LM70, with slightly higher resolution. This patch extends the LM70 driver to support the TMP121. The TMP123 differs in pin assign- ment. Signed-off-by: Manuel Lauss Signed-off-by: Jean Delvare --- Documentation/hwmon/lm70 | 8 ++++- drivers/hwmon/Kconfig | 5 +-- drivers/hwmon/lm70.c | 84 ++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 84 insertions(+), 13 deletions(-) (limited to 'drivers/hwmon/Kconfig') diff --git a/Documentation/hwmon/lm70 b/Documentation/hwmon/lm70 index b8d1a521e68..0d240291e3c 100644 --- a/Documentation/hwmon/lm70 +++ b/Documentation/hwmon/lm70 @@ -1,9 +1,11 @@ Kernel driver lm70 ================== -Supported chip: +Supported chips: * National Semiconductor LM70 Datasheet: http://www.national.com/pf/LM/LM70.html + * Texas Instruments TMP121/TMP123 + Information: http://focus.ti.com/docs/prod/folders/print/tmp121.html Author: Kaiwan N Billimoria @@ -29,6 +31,10 @@ As a real (in-tree) example of this "SPI protocol driver" interfacing with a "SPI master controller driver", see drivers/spi/spi_lm70llp.c and its associated documentation. +The TMP121/TMP123 are very similar; main differences are 4 wire SPI inter- +face (read only) and 13-bit temperature data (0.0625 degrees celsius reso- +lution). + Thanks to --------- Jean Delvare for mentoring the hwmon-side driver diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3c34fb5e419..4b33bc82cc2 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -428,11 +428,12 @@ config SENSORS_LM63 will be called lm63. config SENSORS_LM70 - tristate "National Semiconductor LM70" + tristate "National Semiconductor LM70 / Texas Instruments TMP121" depends on SPI_MASTER && EXPERIMENTAL help If you say yes here you get support for the National Semiconductor - LM70 digital temperature sensor chip. + LM70 and Texas Instruments TMP121/TMP123 digital temperature + sensor chips. This driver can also be built as a module. If so, the module will be called lm70. diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c index 9f9741b1d2b..ae6204f3321 100644 --- a/drivers/hwmon/lm70.c +++ b/drivers/hwmon/lm70.c @@ -37,9 +37,13 @@ #define DRVNAME "lm70" +#define LM70_CHIP_LM70 0 /* original NS LM70 */ +#define LM70_CHIP_TMP121 1 /* TI TMP121/TMP123 */ + struct lm70 { struct device *hwmon_dev; struct mutex lock; + unsigned int chip; }; /* sysfs hook function */ @@ -47,7 +51,7 @@ static ssize_t lm70_sense_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct spi_device *spi = to_spi_device(dev); - int status, val; + int status, val = 0; u8 rxbuf[2]; s16 raw=0; struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); @@ -70,6 +74,7 @@ static ssize_t lm70_sense_temp(struct device *dev, rxbuf[0], rxbuf[1], raw); /* + * LM70: * The "raw" temperature read into rxbuf[] is a 16-bit signed 2's * complement value. Only the MSB 11 bits (1 sign + 10 temperature * bits) are meaningful; the LSB 5 bits are to be discarded. @@ -79,8 +84,21 @@ static ssize_t lm70_sense_temp(struct device *dev, * by 0.25. Also multiply by 1000 to represent in millidegrees * Celsius. * So it's equivalent to multiplying by 0.25 * 1000 = 250. + * + * TMP121/TMP123: + * 13 bits of 2's complement data, discard LSB 3 bits, + * resolution 0.0625 degrees celsius. */ - val = ((int)raw/32) * 250; + switch (p_lm70->chip) { + case LM70_CHIP_LM70: + val = ((int)raw / 32) * 250; + break; + + case LM70_CHIP_TMP121: + val = ((int)raw / 8) * 625 / 10; + break; + } + status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ out: mutex_unlock(&p_lm70->lock); @@ -92,22 +110,31 @@ static DEVICE_ATTR(temp1_input, S_IRUGO, lm70_sense_temp, NULL); static ssize_t lm70_show_name(struct device *dev, struct device_attribute *devattr, char *buf) { - return sprintf(buf, "lm70\n"); + struct lm70 *p_lm70 = dev_get_drvdata(dev); + int ret; + + switch (p_lm70->chip) { + case LM70_CHIP_LM70: + ret = sprintf(buf, "lm70\n"); + break; + case LM70_CHIP_TMP121: + ret = sprintf(buf, "tmp121\n"); + break; + default: + ret = -EINVAL; + } + return ret; } static DEVICE_ATTR(name, S_IRUGO, lm70_show_name, NULL); /*----------------------------------------------------------------------*/ -static int __devinit lm70_probe(struct spi_device *spi) +static int __devinit common_probe(struct spi_device *spi, int chip) { struct lm70 *p_lm70; int status; - /* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */ - if ((spi->mode & (SPI_CPOL|SPI_CPHA)) || !(spi->mode & SPI_3WIRE)) - return -EINVAL; - /* NOTE: we assume 8-bit words, and convert to 16 bits manually */ p_lm70 = kzalloc(sizeof *p_lm70, GFP_KERNEL); @@ -115,6 +142,7 @@ static int __devinit lm70_probe(struct spi_device *spi) return -ENOMEM; mutex_init(&p_lm70->lock); + p_lm70->chip = chip; /* sysfs hook */ p_lm70->hwmon_dev = hwmon_device_register(&spi->dev); @@ -142,6 +170,24 @@ out_dev_reg_failed: return status; } +static int __devinit lm70_probe(struct spi_device *spi) +{ + /* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */ + if ((spi->mode & (SPI_CPOL | SPI_CPHA)) || !(spi->mode & SPI_3WIRE)) + return -EINVAL; + + return common_probe(spi, LM70_CHIP_LM70); +} + +static int __devinit tmp121_probe(struct spi_device *spi) +{ + /* signaling is SPI_MODE_0 with only MISO connected */ + if (spi->mode & (SPI_CPOL | SPI_CPHA)) + return -EINVAL; + + return common_probe(spi, LM70_CHIP_TMP121); +} + static int __devexit lm70_remove(struct spi_device *spi) { struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); @@ -155,6 +201,15 @@ static int __devexit lm70_remove(struct spi_device *spi) return 0; } +static struct spi_driver tmp121_driver = { + .driver = { + .name = "tmp121", + .owner = THIS_MODULE, + }, + .probe = tmp121_probe, + .remove = __devexit_p(lm70_remove), +}; + static struct spi_driver lm70_driver = { .driver = { .name = "lm70", @@ -166,17 +221,26 @@ static struct spi_driver lm70_driver = { static int __init init_lm70(void) { - return spi_register_driver(&lm70_driver); + int ret = spi_register_driver(&lm70_driver); + if (ret) + return ret; + + ret = spi_register_driver(&tmp121_driver); + if (ret) + spi_unregister_driver(&lm70_driver); + + return ret; } static void __exit cleanup_lm70(void) { spi_unregister_driver(&lm70_driver); + spi_unregister_driver(&tmp121_driver); } module_init(init_lm70); module_exit(cleanup_lm70); MODULE_AUTHOR("Kaiwan N Billimoria"); -MODULE_DESCRIPTION("National Semiconductor LM70 Linux driver"); +MODULE_DESCRIPTION("NS LM70 / TI TMP121/TMP123 Linux driver"); MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2