Browse Source

hwmon: (pc87427) Add temperature monitoring support

Add support for the 6 temperature monitoring channels of the PC87427.
Note that the sensors resolution can vary, and I couldn't find a way
to figure it out, so we might have to compensate in user-space.

Signed-off-by: Jean Delvare <khali@linux-fr.org>
Acked-by: Guenter Roeck <guenter.roeck@ericsson.com>
Jean Delvare 14 years ago
parent
commit
008e5f3350
3 changed files with 379 additions and 7 deletions
  1. 13 1
      Documentation/hwmon/pc87427
  2. 2 2
      drivers/hwmon/Kconfig
  3. 364 4
      drivers/hwmon/pc87427.c

+ 13 - 1
Documentation/hwmon/pc87427

@@ -18,7 +18,8 @@ Description
 
 
 The National Semiconductor Super I/O chip includes complete hardware
 The National Semiconductor Super I/O chip includes complete hardware
 monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
 monitoring capabilities. It can monitor up to 18 voltages, 8 fans and
-6 temperature sensors. Only the fans are supported at the moment.
+6 temperature sensors. Only the fans and temperatures are supported at
+the moment, voltages aren't.
 
 
 This chip also has fan controlling features (up to 4 PWM outputs),
 This chip also has fan controlling features (up to 4 PWM outputs),
 which are partly supported by this driver.
 which are partly supported by this driver.
@@ -45,3 +46,14 @@ Fan speed can be controlled by PWM outputs. There are 4 possible modes:
 always off, always on, manual and automatic. The latter isn't supported
 always off, always on, manual and automatic. The latter isn't supported
 by the driver: you can only return to that mode if it was the original
 by the driver: you can only return to that mode if it was the original
 setting, and the configuration interface is missing.
 setting, and the configuration interface is missing.
+
+
+Temperature Monitoring
+----------------------
+
+The PC87427 relies on external sensors (following the SensorPath
+standard), so the resolution and range depend on the type of sensor
+connected. The integer part can be 8-bit or 9-bit, and can be signed or
+not. I couldn't find a way to figure out the external sensor data
+temperature format, so user-space adjustment (typically by a factor 2)
+may be required.

+ 2 - 2
drivers/hwmon/Kconfig

@@ -711,8 +711,8 @@ config SENSORS_PC87427
 	  functions of the National Semiconductor PC87427 Super-I/O chip.
 	  functions of the National Semiconductor PC87427 Super-I/O chip.
 	  The chip has two distinct logical devices, one for fan speed
 	  The chip has two distinct logical devices, one for fan speed
 	  monitoring and control, and one for voltage and temperature
 	  monitoring and control, and one for voltage and temperature
-	  monitoring. Only fan speed monitoring and control 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
 	  This driver can also be built as a module.  If so, the module
 	  will be called pc87427.
 	  will be called pc87427.

+ 364 - 4
drivers/hwmon/pc87427.c

@@ -1,7 +1,7 @@
 /*
 /*
  *  pc87427.c - hardware monitoring driver for the
  *  pc87427.c - hardware monitoring driver for the
  *              National Semiconductor PC87427 Super-I/O chip
  *              National Semiconductor PC87427 Super-I/O chip
- *  Copyright (C) 2006, 2008  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
  *  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
  *  it under the terms of the GNU General Public License version 2 as
@@ -15,10 +15,11 @@
  *  Supports the following chips:
  *  Supports the following chips:
  *
  *
  *  Chip        #vin    #fan    #pwm    #temp   devid
  *  Chip        #vin    #fan    #pwm    #temp   devid
- *  PC87427     -       8       4       -       0xF2
+ *  PC87427     -       8       4       6       0xF2
  *
  *
  *  This driver assumes that no more than one chip is present.
  *  This driver assumes that no more than one chip is present.
- *  Only fans 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>
 #include <linux/module.h>
@@ -62,6 +63,14 @@ struct pc87427_data {
 	u8 pwm_auto_ok;			/* bit vector */
 	u8 pwm_auto_ok;			/* bit vector */
 	u8 pwm_enable[4];		/* register values */
 	u8 pwm_enable[4];		/* register values */
 	u8 pwm[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 {
 struct pc87427_sio_data {
@@ -120,6 +129,8 @@ static inline void superio_exit(int sioaddr)
 #define BANK_FM(nr)		(nr)
 #define BANK_FM(nr)		(nr)
 #define BANK_FT(nr)		(0x08 + (nr))
 #define BANK_FT(nr)		(0x08 + (nr))
 #define BANK_FC(nr)		(0x10 + (nr) * 2)
 #define BANK_FC(nr)		(0x10 + (nr) * 2)
+#define BANK_TM(nr)		(nr)
+#define BANK_VM(nr)		(0x08 + (nr))
 
 
 /*
 /*
  * I/O access functions
  * I/O access functions
@@ -251,6 +262,72 @@ static inline u8 pwm_enable_to_reg(unsigned long val, u8 pwmval)
 	}
 	}
 }
 }
 
 
+/*
+ * 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
  * Data interface
  */
  */
@@ -279,6 +356,13 @@ static struct pc87427_data *pc87427_update_device(struct device *dev)
 		pc87427_readall_pwm(data, i);
 		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;
 	data->last_updated = jiffies;
 
 
 done:
 done:
@@ -595,6 +679,251 @@ static const struct attribute_group pc87427_group_pwm[4] = {
 	{ .attrs = pc87427_attributes_pwm[3] },
 	{ .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
 static ssize_t show_name(struct device *dev, struct device_attribute
 			 *devattr, char *buf)
 			 *devattr, char *buf)
 {
 {
@@ -659,7 +988,7 @@ static void __devinit pc87427_init_device(struct device *dev)
 	/* The FMC module should be ready */
 	/* The FMC module should be ready */
 	reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK);
 	reg = pc87427_read8(data, LD_FAN, PC87427_REG_BANK);
 	if (!(reg & 0x80))
 	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 */
 	/* Check which fans are enabled */
 	for (i = 0; i < 8; i++) {
 	for (i = 0; i < 8; i++) {
@@ -701,6 +1030,19 @@ static void __devinit pc87427_init_device(struct device *dev)
 			data->pwm_auto_ok |= (1 << i);
 			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 int __devinit pc87427_probe(struct platform_device *pdev)
 static int __devinit pc87427_probe(struct platform_device *pdev)
@@ -749,6 +1091,14 @@ static int __devinit pc87427_probe(struct platform_device *pdev)
 		if (err)
 		if (err)
 			goto exit_remove_files;
 			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;
+	}
 
 
 	data->hwmon_dev = hwmon_device_register(&pdev->dev);
 	data->hwmon_dev = hwmon_device_register(&pdev->dev);
 	if (IS_ERR(data->hwmon_dev)) {
 	if (IS_ERR(data->hwmon_dev)) {
@@ -770,6 +1120,11 @@ exit_remove_files:
 			continue;
 			continue;
 		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
 		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
 	}
 	}
+	for (i = 0; i < 6; i++) {
+		if (!(data->temp_enabled & (1 << i)))
+			continue;
+		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_temp[i]);
+	}
 exit_release_region:
 exit_release_region:
 	pc87427_release_regions(pdev, res_count);
 	pc87427_release_regions(pdev, res_count);
 exit_kfree:
 exit_kfree:
@@ -798,6 +1153,11 @@ static int __devexit pc87427_remove(struct platform_device *pdev)
 			continue;
 			continue;
 		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
 		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_pwm[i]);
 	}
 	}
+	for (i = 0; i < 6; i++) {
+		if (!(data->temp_enabled & (1 << i)))
+			continue;
+		sysfs_remove_group(&pdev->dev.kobj, &pc87427_group_temp[i]);
+	}
 	platform_set_drvdata(pdev, NULL);
 	platform_set_drvdata(pdev, NULL);
 	kfree(data);
 	kfree(data);