summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--CONTRIBUTORS3
-rw-r--r--README2
-rw-r--r--doc/chips/max6650125
-rw-r--r--kernel/chips/max6650.c539
-rw-r--r--lib/chips.c20
-rw-r--r--lib/chips.h10
-rwxr-xr-xprog/detect/sensors-detect36
-rw-r--r--prog/sensors/chips.c49
-rw-r--r--prog/sensors/chips.h1
-rw-r--r--prog/sensors/main.c2
11 files changed, 786 insertions, 2 deletions
diff --git a/CHANGES b/CHANGES
index b2aa841a..e199c5f1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -58,6 +58,7 @@ ask CVS about it:
Module lm78: Fix a fan_div/fan_min bug
Module lm83: Add t_crit and alarms support
Module lm90: New
+ Module max6650: New
Module saa1064: New
Module w83781d: Enhance chip detection
Module xeontemp: New
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 8df163f5..68ff6080 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -90,3 +90,6 @@ problems.
Author of the ADM1026 chip driver.
* Sascha Volkenandt <sascha@akv-soft.de>
Author of the SAA1064 chip driver.
+* John Morris (john.morris@spirentcom.com)
+ Author of the MAX6650 chip driver.
+
diff --git a/README b/README
index ba0a38a5..6e6ab409 100644
--- a/README
+++ b/README
@@ -77,7 +77,7 @@ At least the following hardware sensor chips are supported:
Genesys Logic GL518SM (rev 00, 80), GL520SM, GL523SM
Intel Xeon processor embedded sensors
ITE IT8705F, IT8712F embedded sensors
- Maxim MAX1617 and MAX1617A
+ Maxim MAX1617, MAX1617A, MAX6650 and MAX6651
Microchip TC1068, TCM1617, TCN75
Myson MTP008
National Semiconductor LM75, LM77, LM78, LM78-J, LM79,
diff --git a/doc/chips/max6650 b/doc/chips/max6650
new file mode 100644
index 00000000..36b135c5
--- /dev/null
+++ b/doc/chips/max6650
@@ -0,0 +1,125 @@
+Sensors chip driver for MAXIM MAX6650 / MAX6651 fan controllers
+===============================================================
+
+Chip notes
+----------
+
+The MAX6551 is a fan controller. It has two distinct functions:
+
+1) Speed control, with feedback, for a single fan. The code can write
+ to a register requesting that the fan run at a certain speed, and the
+ chip will adjust the fan voltage until it reaches that speed. This
+ is called "closed loop" mode in the datasheet.
+
+2) Speed measurement (tachometers) for up to four fans, including the
+ controlled one.
+
+The MAX6550 is similar but only has one measurement tachometer.
+
+Both chips have a few general purpose i/o pins, some of which can be
+configured to have special functions. At the time of writing an overview
+of the chip, with links to the datasheet, can be seen at:
+
+ http://www.maxim-ic.com/quick_view2.cfm/qv_pk/2296/ln/en
+
+If that has moved searching for MAX6550 from Maxim's home page should
+work.
+
+Driver state
+------------
+
+Currently the driver provides the following entries in /proc/sys/
+dev/sensors/max6650-xxx/:
+
+fan1 through fan4 (read only): the current measured fan speeds, in
+rpm.
+
+speed (read/write): when written, sets the chip to closed loop mode and
+sets the configured speed, in rpm, to the value written. When read returns
+the configured speed, again in rpm.
+
+xdump (read only): returns all of the chip register values, except the
+tachometers. Used for debug, and should probably go away or be conditionally
+compiled.
+
+The MAX665x uses byte wide registers with various scaling factors and
+some more or less complex equations (in the datasheet) relating register
+values to real world values. The driver takes care of all of this.
+
+Note that the measured speed ("fan1") never exactly matches the set speed
+("speed") because of the coarse resolution of the underlying registers.
+
+Assumptions
+-----------
+
+The driver assumes that:
+
+ o The MAX665x is running from its internal 254 kHz clock, not an
+ external clock.
+
+ o The monitored fans produce two pulses per rotation (This same
+ assumption is made by Maxim throughout the datasheet.)
+
+ o The tachometer count time (register 0x16) has been configured suitably
+ for the fans in use, perhaps by some boot code. The driver reads this
+ register to compute rpm, but never writes to it.
+
+ o The speed controller prescale (low three bits of register 0x04) has
+ been configured suitably for the fans in use, perhaps by some boot
+ code. The driver reads these bits to compute rpm, but never changes
+ them.
+
+The driver currently never reads or writes any other configuration
+(gpio config, alarms, 5V/12V setting).
+
+Bugs
+----
+
+ o The driver assumes too much: it should be possible to set more of the
+ configuration dynamically instead of assuming that some other code has
+ done it.
+
+ o It does not differentiate between a MAX6550 and a MAX6551, as I can
+ see no obvious way to do this. It has only been tested with a MAX6551
+ so if anyone gets a board with a MAX6550 to test with maybe something
+ will be revealed, such as the three missing tachometer registers
+ returning all ones or something. (OK, so maybe I should have called
+ this driver max6651, and ignored the max6650 totally...)
+
+ o The /proc names don't match the standards in proc/sevelopers/proc,
+ as this chip doesn't map well to other fan controllers,
+
+Detection notes
+---------------
+
+The module will autodetect the presence of a max6650 or max6651 at any
+of the four possible I2C addresses for these devices. There is no ID
+register, but there are several registers with all zero bits at the
+high end. The driver checks that all of these are in fact zero before
+declaring the chip to be a MAX6550/1.
+
+sensors-detect
+--------------
+
+Support for MAX6650/1 has been added to sensors-detect. It has been
+tested as correctly detecting a MAX6551 at I2C address 0x1B. Like the
+driver, it cannot yet distinguish a MAX6650 and MAX6651.
+
+prog/sensors
+------------
+
+Initial read-only support has been added to the "sensors" program.
+
+Testing
+-------
+
+The driver and associated programs have been tested under Linux 2.4.18
+running on an embedded Intel XScale processor (actually an IXP425 running
+in big-endian mode) with a MAX6651 chip.
+
+Author
+------
+
+John Morris (john.morris@spirentcom.com)
+
+
diff --git a/kernel/chips/max6650.c b/kernel/chips/max6650.c
new file mode 100644
index 00000000..583d6051
--- /dev/null
+++ b/kernel/chips/max6650.c
@@ -0,0 +1,539 @@
+/*
+ * max6650.c - Part of lm_sensors, Linux kernel modules for hardware
+ * monitoring.
+ *
+ * Author: John Morris <john.morris@spirentcom.com>
+ *
+ * Copyright (c) 2003 Spirent Communications
+ *
+ * This module has only been tested with the MAX6651 chip. It should
+ * work with the MAX6650 also, though with reduced functionality. It
+ * does not yet distinguish max6650 and max6651 chips.
+ *
+ * Tha datasheet was last seen at:
+ *
+ * http://pdfserv.maxim-ic.com/en/ds/MAX6650-MAX6651.pdf
+ *
+ * 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/slab.h>
+#include <linux/i2c.h>
+#include <linux/i2c-proc.h>
+#include <linux/i2c-id.h>
+#include <linux/init.h>
+#include "version.h"
+
+/*
+ * Addresses to scan. There are four disjoint possibilities, by pin config.
+ */
+
+static unsigned short normal_i2c[] = {0x1b, 0x1f, 0x48, 0x4b, SENSORS_I2C_END};
+static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
+static unsigned int normal_isa[] = { SENSORS_ISA_END };
+static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
+
+/*
+ * Insmod parameters
+ */
+
+SENSORS_INSMOD_1(max6650);
+
+/*
+ * MAX 6650/6651 registers
+ */
+
+#define MAX6650_REG_SPEED 0x00
+#define MAX6650_REG_CONFIG 0x02
+#define MAX6650_REG_GPIO_DEF 0x04
+#define MAX6650_REG_DAC 0x06
+#define MAX6650_REG_ALARM_EN 0x08
+#define MAX6650_REG_ALARM 0x0A
+#define MAX6650_REG_TACH0 0x0C
+#define MAX6650_REG_TACH1 0x0E
+#define MAX6650_REG_TACH2 0x10
+#define MAX6650_REG_TACH3 0x12
+#define MAX6650_REG_GPIO_STAT 0x14
+#define MAX6650_REG_COUNT 0x16
+
+/*
+ * Config register bits
+ */
+
+#define MAX6650_CFG_MODE_MASK 0x30
+#define MAX6650_CFG_MODE_ON 0x00
+#define MAX6650_CFG_MODE_OFF 0x10
+#define MAX6650_CFG_MODE_CLOSED_LOOP 0x20
+#define MAX6650_CFG_MODE_OPEN_LOOP 0x30
+
+static const u8 tach_reg[] =
+{
+ MAX6650_REG_TACH0, MAX6650_REG_TACH1,
+ MAX6650_REG_TACH2, MAX6650_REG_TACH3
+};
+
+#define MAX6650_INT_CLK 254000 /* Default clock speed - 254 kHz */
+
+/*
+ * Functions declaration
+ */
+
+static void max6650_fan (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results);
+static void max6650_speed (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results);
+static void max6650_xdump (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results);
+static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned
+ short flags, int kind);
+static int max6650_attach_adapter(struct i2c_adapter *adapter);
+static int max6650_detach_client(struct i2c_client *client);
+static void max6650_init_client(struct i2c_client *client);
+static int max6650_read(struct i2c_client *client, u8 reg);
+
+/*
+ * Driver data (common to all clients)
+ */
+
+
+static struct i2c_driver max6650_driver = {
+ .owner = THIS_MODULE,
+ .name = "MAX6650/1 sensor driver",
+ .id = I2C_DRIVERID_MAX6650,
+ .flags = I2C_DF_NOTIFY,
+ .attach_adapter = max6650_attach_adapter,
+ .detach_client = max6650_detach_client
+};
+
+/*
+ * Client data (each client gets its own)
+ */
+
+struct max6650_data
+{
+ int sysctl_id;
+ struct semaphore update_lock;
+ char valid; /* zero until following fields are valid */
+ unsigned long last_updated; /* in jiffies */
+
+ /* register values */
+
+ u8 speed;
+ u8 config;
+ u8 tach[4];
+ u8 count;
+};
+
+/*
+ * Proc entries
+ * These files are created for each detected max6650.
+ */
+
+/* -- SENSORS SYSCTL START -- */
+
+#define MAX6650_SYSCTL_FAN1 1101
+#define MAX6650_SYSCTL_FAN2 1102
+#define MAX6650_SYSCTL_FAN3 1103
+#define MAX6650_SYSCTL_FAN4 1104
+#define MAX6650_SYSCTL_SPEED 1105
+#define MAX6650_SYSCTL_XDUMP 1106
+
+
+/* -- SENSORS SYSCTL END -- */
+
+
+static ctl_table max6650_dir_table_template[] =
+{
+ {MAX6650_SYSCTL_FAN1, "fan1", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan},
+ {MAX6650_SYSCTL_FAN2, "fan2", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan},
+ {MAX6650_SYSCTL_FAN3, "fan3", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan},
+ {MAX6650_SYSCTL_FAN4, "fan4", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_fan},
+ {MAX6650_SYSCTL_SPEED, "speed", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_speed},
+ {MAX6650_SYSCTL_XDUMP, "xdump", NULL, 0, 0644, NULL,
+ &i2c_proc_real, &i2c_sysctl_real, NULL, &max6650_xdump},
+ {0}
+};
+
+/*
+ * Internal variables
+ */
+
+static int max6650_id = 0;
+
+/*
+ * Real code
+ */
+
+static int max6650_attach_adapter(struct i2c_adapter *adapter)
+{
+ return i2c_detect(adapter, &addr_data, max6650_detect);
+}
+
+/*
+ * The following function does more than just detection. If detection
+ * succeeds, it also registers the new chip.
+ */
+
+static int max6650_detect(struct i2c_adapter *adapter, int address, unsigned
+ short flags, int kind)
+{
+ struct i2c_client *new_client;
+ struct max6650_data *data;
+ int err = 0;
+ const char *type_name = "";
+ const char *client_name = "";
+
+#ifdef DEBUG
+ if (i2c_is_isa_adapter(adapter)) {
+ printk("max6650.o: Called for an ISA bus adapter, aborting.\n");
+ return 0;
+ }
+#endif
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+#ifdef DEBUG
+ printk("max6650.o: I2C bus doesn't support byte read mode, skipping.\n");
+#endif
+ return 0;
+ }
+
+ if (!(new_client = kmalloc(sizeof(struct i2c_client) + sizeof(struct
+ max6650_data), GFP_KERNEL))) {
+ printk("max6650.o: Out of memory in max6650_detect (new_client).\n");
+ return -ENOMEM;
+ }
+
+ /*
+ * The max6650-specific data is placed right after the common I2C
+ * client data, and is pointed to by the data field from the I2C
+ * client data.
+ */
+
+ new_client->addr = address;
+ new_client->data = data = (struct max6650_data *) (new_client + 1);
+ new_client->adapter = adapter;
+ new_client->driver = &max6650_driver;
+ new_client->flags = 0;
+
+ /*
+ * Now we do the remaining detection. A negative kind means that
+ * the driver was loaded with no force parameter (default), so we
+ * must both detect and identify the chip (actually there is only
+ * one possible kind of chip for now, max6650). A zero kind means that
+ * the driver was loaded with the force parameter, the detection
+ * step shall be skipped. A positive kind means that the driver
+ * was loaded with the force parameter and a given kind of chip is
+ * requested, so both the detection and the identification steps
+ * are skipped.
+ *
+ * Currently I can find no way to distinguish between a MAX6650 and
+ * a MAX6651. This driver has only been tried on the latter.
+ */
+
+ if (kind < 0) { /* detection */
+ if (
+ (max6650_read(new_client, MAX6650_REG_CONFIG) & 0xC0) ||
+ (max6650_read(new_client, MAX6650_REG_GPIO_STAT) & 0xE0) ||
+ (max6650_read(new_client, MAX6650_REG_ALARM_EN) & 0xE0) ||
+ (max6650_read(new_client, MAX6650_REG_ALARM) & 0xE0) ||
+ (max6650_read(new_client, MAX6650_REG_COUNT) & 0xFC)
+ )
+ {
+#ifdef DEBUG
+ printk("max6650.o: max6650 detection failed at 0x%02x.\n",
+ address);
+#endif
+ goto ERROR1;
+ }
+ }
+
+ if (kind <= 0) { /* identification */
+ kind = max6650;
+ }
+
+ if (kind <= 0) { /* identification failed */
+ printk("max6650.o: Unsupported chip.\n");
+ goto ERROR1;
+ }
+
+ if (kind == max6650) {
+ type_name = "max6650";
+ client_name = "max6650 chip";
+ } else {
+ printk("max6650.o: Unknown kind %d.\n", kind);
+ goto ERROR1;
+ }
+
+ /*
+ * OK, we got a valid chip so we can fill in the remaining client
+ * fields.
+ */
+
+ strcpy(new_client->name, client_name);
+ new_client->id = max6650_id++;
+ data->valid = 0;
+ init_MUTEX(&data->update_lock);
+
+ /*
+ * Tell the I2C layer a new client has arrived.
+ */
+
+ if ((err = i2c_attach_client(new_client))) {
+#ifdef DEBUG
+ printk("max6650.o: Failed attaching client.\n");
+#endif
+ goto ERROR1;
+ }
+
+ /*
+ * Register a new directory entry.
+ */
+ if ((err = i2c_register_entry(new_client, type_name,
+ max6650_dir_table_template)) < 0) {
+#ifdef DEBUG
+ printk("max6650.o: Failed registering directory entry.\n");
+#endif
+ goto ERROR2;
+ }
+ data->sysctl_id = err;
+
+ /*
+ * Initialize the max6650 chip
+ */
+ max6650_init_client(new_client);
+ return 0;
+
+ERROR2:
+ i2c_detach_client(new_client);
+ERROR1:
+ kfree(new_client);
+ return err;
+}
+
+static void max6650_init_client(struct i2c_client *client)
+{
+ /* Nothing to do here - assume the BIOS has initialized the chip */
+}
+
+static int max6650_detach_client(struct i2c_client *client)
+{
+ int err;
+
+ i2c_deregister_entry(((struct max6650_data *) (client->data))->sysctl_id);
+ if ((err = i2c_detach_client(client))) {
+ printk("max6650.o: Client deregistration failed, "
+ "client not detached.\n");
+ return err;
+ }
+
+ kfree(client);
+ return 0;
+}
+
+static int max6650_read(struct i2c_client *client, u8 reg)
+{
+ return i2c_smbus_read_byte_data(client, reg);
+}
+
+static int max6650_write(struct i2c_client *client, u8 reg, u8 value)
+{
+ return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+static void max6650_update_client(struct i2c_client *client)
+{
+ int i;
+ struct max6650_data *data = client->data;
+
+ down(&data->update_lock);
+
+ if ((jiffies - data->last_updated > HZ) ||
+ (jiffies < data->last_updated) || !data->valid) {
+#ifdef DEBUG
+ printk("max6650.o: Updating max6650 data.\n");
+#endif
+ data->speed = max6650_read (client, MAX6650_REG_SPEED);
+ data->config = max6650_read (client, MAX6650_REG_CONFIG);
+ for (i = 0; i < 4; i++) {
+ data->tach[i] = max6650_read(client, tach_reg[i]);
+ }
+ data->count = max6650_read (client, MAX6650_REG_COUNT);
+ data->last_updated = jiffies;
+ data->valid = 1;
+ }
+ up(&data->update_lock);
+}
+
+static void max6650_fan (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results)
+{
+ int index = ctl_name - MAX6650_SYSCTL_FAN1;
+ struct max6650_data *data = client->data;
+ int tcount; /* Tachometer count time, 0.25 second units */
+
+ if (operation == SENSORS_PROC_REAL_INFO) {
+ *nrels_mag = 0;
+ } else if (operation == SENSORS_PROC_REAL_READ) {
+ max6650_update_client(client);
+
+ /*
+ * Calculation details:
+ *
+ * Each tachometer counts over an interval given by the "count"
+ * register (0.25, 0.5, 1 or 2 seconds). This module assumes
+ * that the fans produce two pulses per revolution (this seems
+ * to be the most common).
+ */
+
+ tcount = 1 << data->count; /* 0.25 second units */
+ results[0] = (data->tach[index] * 240) / tcount; /* counts per min */
+ results[0] /= 2; /* Assume two counts per rev */
+ *nrels_mag = 1;
+ }
+}
+
+/*
+ * Set the fan speed to the specified RPM (or read back the RPM setting).
+ *
+ * The MAX6650/1 will automatically control fan speed when in closed loop
+ * mode.
+ *
+ * Assumptions:
+ *
+ * 1) The MAX6650/1 is running from its internal 254kHz clock (perhaps
+ * this should be made a module parameter).
+ *
+ * 2) The prescaler (low three bits of the config register) has already
+ * been set to an appropriate value.
+ *
+ * The relevant equations are given on pages 21 and 22 of the datasheet.
+ *
+ * From the datasheet, the relevant equation when in regulation is:
+ *
+ * [fCLK / (128 x (KTACH + 1))] = 2 x FanSpeed / KSCALE
+ *
+ * where:
+ *
+ * fCLK is the oscillator frequency (either the 254kHz internal
+ * oscillator or the externally applied clock)
+ *
+ * KTACH is the value in the speed register
+ *
+ * FanSpeed is the speed of the fan in rps
+ *
+ * KSCALE is the prescaler value (1, 2, 4, 8, or 16)
+ *
+ * When reading, we need to solve for FanSpeed. When writing, we need to
+ * solve for KTACH.
+ *
+ * Note: this tachometer is completely separate from the tachometers
+ * used to measure the fan speeds. Only one fan's speed (fan1) is
+ * controlled.
+ */
+
+static void max6650_speed (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results)
+{
+ struct max6650_data *data = client->data;
+ int kscale, ktach, fclk, rpm;
+
+ if (operation == SENSORS_PROC_REAL_INFO) {
+ *nrels_mag = 0;
+ } else if (operation == SENSORS_PROC_REAL_READ) {
+ /*
+ * Use the datasheet equation:
+ *
+ * FanSpeed = KSCALE x fCLK / [256 x (KTACH + 1)]
+ *
+ * then multiply by 60 to give rpm.
+ */
+
+ max6650_update_client(client);
+
+ kscale = 1 << (data->config & 7);
+ ktach = data->speed;
+ fclk = MAX6650_INT_CLK;
+ rpm = 60 * kscale * fclk / (256 * (ktach + 1));
+
+ results[0] = rpm;
+ *nrels_mag = 1;
+ } else if (operation == SENSORS_PROC_REAL_WRITE && *nrels_mag >= 1) {
+ /*
+ * Divide the required speed by 60 to get from rpm to rps, then
+ * use the datasheet equation:
+ *
+ * KTACH = [(fCLK x KSCALE) / (256 x FanSpeed)] - 1
+ */
+
+ max6650_update_client(client);
+
+ rpm = results[0];
+ kscale = 1 << (data->config & 7);
+ fclk = MAX6650_INT_CLK;
+ ktach = ((fclk * kscale) / (256 * rpm / 60)) - 1;
+
+ data->speed = ktach;
+ data->config = (data->config & ~MAX6650_CFG_MODE_MASK) |
+ MAX6650_CFG_MODE_CLOSED_LOOP;
+ max6650_write (client, MAX6650_REG_CONFIG, data->config);
+ max6650_write (client, MAX6650_REG_SPEED, data->speed);
+ }
+}
+
+/*
+ * Debug - dump all registers except the tach counts.
+ */
+
+static void max6650_xdump (struct i2c_client *client, int operation, int
+ ctl_name, int *nrels_mag, long *results)
+{
+ if (operation == SENSORS_PROC_REAL_INFO) {
+ *nrels_mag = 0;
+ } else if (operation == SENSORS_PROC_REAL_READ) {
+ results[0] = max6650_read (client, MAX6650_REG_SPEED);
+ results[1] = max6650_read (client, MAX6650_REG_CONFIG);
+ results[2] = max6650_read (client, MAX6650_REG_GPIO_DEF);
+ results[3] = max6650_read (client, MAX6650_REG_DAC);
+ results[4] = max6650_read (client, MAX6650_REG_ALARM_EN);
+ results[5] = max6650_read (client, MAX6650_REG_ALARM);
+ results[6] = max6650_read (client, MAX6650_REG_GPIO_STAT);
+ results[7] = max6650_read (client, MAX6650_REG_COUNT);
+ *nrels_mag = 8;
+ }
+}
+
+static int __init sm_max6650_init(void)
+{
+ printk(KERN_INFO "max6650.o version %s (%s)\n", LM_VERSION, LM_DATE);
+ return i2c_add_driver(&max6650_driver);
+}
+
+static void __exit sm_max6650_exit(void)
+{
+ i2c_del_driver(&max6650_driver);
+}
+
+MODULE_AUTHOR("john.morris@spirentcom.com");
+MODULE_DESCRIPTION("max6650 sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(sm_max6650_init);
+module_exit(sm_max6650_exit);
diff --git a/lib/chips.c b/lib/chips.c
index bab2a0a0..dbae4bab 100644
--- a/lib/chips.c
+++ b/lib/chips.c
@@ -4443,6 +4443,25 @@ static sensors_chip_feature xeontemp_features[] =
{ 0 }
};
+static sensors_chip_feature max6650_features[] =
+ {
+ { SENSORS_MAX6650_FAN1_TACH, "fan1", SENSORS_NO_MAPPING,
+ SENSORS_NO_MAPPING, SENSORS_MODE_R,
+ MAX6650_SYSCTL_FAN1, VALUE(1), 0 },
+ { SENSORS_MAX6650_FAN2_TACH, "fan2", SENSORS_NO_MAPPING,
+ SENSORS_NO_MAPPING, SENSORS_MODE_R,
+ MAX6650_SYSCTL_FAN2, VALUE(1), 0 },
+ { SENSORS_MAX6650_FAN3_TACH, "fan3", SENSORS_NO_MAPPING,
+ SENSORS_NO_MAPPING, SENSORS_MODE_R,
+ MAX6650_SYSCTL_FAN3, VALUE(1), 0 },
+ { SENSORS_MAX6650_FAN4_TACH, "fan4", SENSORS_NO_MAPPING,
+ SENSORS_NO_MAPPING, SENSORS_MODE_R,
+ MAX6650_SYSCTL_FAN4, VALUE(1), 0 },
+ { SENSORS_MAX6650_SPEED, "speed", SENSORS_NO_MAPPING,
+ SENSORS_NO_MAPPING, SENSORS_MODE_RW,
+ MAX6650_SYSCTL_SPEED, VALUE(1), 0 },
+ };
+
sensors_chip_features sensors_chip_features_list[] =
{
{ SENSORS_LM78_PREFIX, lm78_features },
@@ -4511,5 +4530,6 @@ sensors_chip_features sensors_chip_features_list[] =
{ SENSORS_LM83_PREFIX, lm83_features },
{ SENSORS_LM90_PREFIX, lm90_features },
{ SENSORS_XEONTEMP_PREFIX, xeontemp_features },
+ { SENSORS_MAX6650_PREFIX, max6650_features },
{ 0 }
};
diff --git a/lib/chips.h b/lib/chips.h
index 857b88c4..6b672860 100644
--- a/lib/chips.h
+++ b/lib/chips.h
@@ -1655,4 +1655,14 @@
#define SENSORS_XEONTEMP_REMOTE_TEMP_OVER 56 /* RW */
#define SENSORS_XEONTEMP_ALARMS 81 /* R */
+/* MAX6650 / 1 chips */
+
+#define SENSORS_MAX6650_PREFIX "max6650"
+
+#define SENSORS_MAX6650_FAN1_TACH 1 /* R */
+#define SENSORS_MAX6650_FAN2_TACH 2 /* R */
+#define SENSORS_MAX6650_FAN3_TACH 3 /* R */
+#define SENSORS_MAX6650_FAN4_TACH 4 /* R */
+#define SENSORS_MAX6650_SPEED 5 /* RW */
+
#endif /* def LIB_SENSORS_CHIPS_H */
diff --git a/prog/detect/sensors-detect b/prog/detect/sensors-detect
index 1d402fe8..2574bdfd 100755
--- a/prog/detect/sensors-detect
+++ b/prog/detect/sensors-detect
@@ -776,7 +776,7 @@ use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect
fscscy_detect pcf8591_detect arp_detect ipmi_kcs_detect
ipmi_smic_detect via8231_isa_detect lm85_detect smartbatt_detect
adm1026_detect w83l785ts_detect lm83_detect lm90_detect
- saa1064_detect w83l784r_detect mozart_detect);
+ saa1064_detect w83l784r_detect mozart_detect max6650_detect);
# This is a list of all recognized chips.
# Each entry must have the following fields:
@@ -1061,6 +1061,12 @@ use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect
i2c_detect => sub { adm1021_detect 3, @_ },
},
{
+ name => "Maxim MAX6650/MAX6651",
+ driver => "max6650",
+ i2c_addrs => [0x1b,0x1f,0x48,0x4b],
+ i2c_detect => sub { max6650_detect 0, @_ },
+ },
+ {
name => "TI THMC10",
driver => "adm1021",
i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
@@ -3400,6 +3406,34 @@ sub w83l785ts_detect
return (8);
}
+# $_[0]: Chip to detect. Always zero for now, but available for future use
+# if somebody finds a way to distinguish MAX6650 and MAX6651.
+# $_[1]: A reference to the file descriptor to access this chip.
+# We may assume an i2c_set_slave_addr was already done.
+# $_[2]: Address
+# Returns: undef if not detected, 4 if detected.
+#
+# The max6650 has no device ID register. However, a few registers have
+# spare bits, which are documented as being always zero on read. We read
+# all of these registers check the spare bits. Any non-zero means this
+# is not a max6650/1.
+#
+# The always zero bits are:
+# configuration byte register (0x02) - top 2 bits
+# gpio status register (0x14) - top 3 bits
+# alarm enable register (0x08) - top 3 bits
+# alarm status register (0x0A) - top 3 bits
+# tachometer count time register (0x16) - top 6 bits
+sub max6650_detect
+{
+ my ($chip, $file) = @_;
+ return if i2c_smbus_read_byte_data($file,0x16) & 0xFC;
+ return if i2c_smbus_read_byte_data($file,0x0A) & 0xE0;
+ return if i2c_smbus_read_byte_data($file,0x08) & 0xE0;
+ return if i2c_smbus_read_byte_data($file,0x14) & 0xE0;
+ return if i2c_smbus_read_byte_data($file,0x02) & 0xC0;
+ return 4;
+}
################
diff --git a/prog/sensors/chips.c b/prog/sensors/chips.c
index 2b078fa5..5bd1d92a 100644
--- a/prog/sensors/chips.c
+++ b/prog/sensors/chips.c
@@ -4511,6 +4511,55 @@ void print_xeontemp(const sensors_chip_name *name)
free_the_label(&label);
}
+void print_max6650(const sensors_chip_name *name)
+{
+ char *label = NULL;
+ double tach, speed;
+ int valid, i;
+
+ static const struct
+ {
+ int tag;
+ char *name;
+ }
+ tach_list[] =
+ {
+ { SENSORS_MAX6650_FAN1_TACH, "FAN1" },
+ { SENSORS_MAX6650_FAN2_TACH, "FAN2" },
+ { SENSORS_MAX6650_FAN3_TACH, "FAN3" },
+ { SENSORS_MAX6650_FAN4_TACH, "FAN4" }
+ };
+
+ /* Display full config for fan1, which is controlled */
+
+ if (!sensors_get_label_and_valid(*name,tach_list[0].tag,&label,&valid) &&
+ !sensors_get_feature(*name,tach_list[0].tag,&tach) &&
+ !sensors_get_feature(*name,SENSORS_MAX6650_SPEED,&speed)) {
+ if (valid) {
+ print_label(label,10);
+ printf("configured %4.0f RPM, actual %4.0f RPM.\n", speed, tach);
+ }
+ } else
+ printf("ERROR: Can't get %s data!\n", tach_list[i].name);
+ free_the_label(&label);
+
+ /* Just display the measured speed for the other three, uncontrolled fans */
+
+ for (i = 1; i < 4; i++)
+ {
+ if (!sensors_get_label_and_valid(*name,tach_list[i].tag,&label,&valid) &&
+ !sensors_get_feature(*name,tach_list[i].tag,&tach)) {
+ if (valid) {
+ print_label(label,10);
+ printf("%4.0f RPM \n", tach);
+ }
+ } else
+ printf("ERROR: Can't get %s data!\n", tach_list[i].name);
+
+ free_the_label(&label);
+ }
+}
+
void print_unknown_chip(const sensors_chip_name *name)
{
int a,b,valid;
diff --git a/prog/sensors/chips.h b/prog/sensors/chips.h
index 4b8baa16..3de1a2c7 100644
--- a/prog/sensors/chips.h
+++ b/prog/sensors/chips.h
@@ -55,5 +55,6 @@ extern void print_bmc(const sensors_chip_name *name);
extern void print_lm83(const sensors_chip_name *name);
extern void print_lm90(const sensors_chip_name *name);
extern void print_xeontemp(const sensors_chip_name *name);
+extern void print_max6650(const sensors_chip_name *name);
#endif /* def PROG_SENSORS_CHIPS_H */
diff --git a/prog/sensors/main.c b/prog/sensors/main.c
index 37fc89a3..d4088f13 100644
--- a/prog/sensors/main.c
+++ b/prog/sensors/main.c
@@ -380,6 +380,8 @@ void do_a_print(sensors_chip_name name)
print_lm90(&name);
else if (!strcmp(name.prefix,"xeontemp"))
print_xeontemp(&name);
+ else if (!strcmp(name.prefix,"max6650"))
+ print_max6650(&name);
else
print_unknown_chip(&name);
printf("\n");