mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-10-27 12:30:43 +00:00
(jrm): New driver and other support for the Maxim MAX6650 and MAX6651 chips.
Mainly targetted (and tested) with the 6651, but should work with
the MAX6550.
See doc/chips/max6650 for notes, bugs, assumptions, etc.
git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@1987 7894878c-1315-0410-8ee3-d5d059ff63e0
This commit is contained in:
1 parent
ebb1dc9a20
commit
45576fb3fd
11 files changed
+786
-2
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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);
|
||||
+20
@@ -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 }
|
||||
};
|
||||
+10
@@ -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 */
|
||||
@@ -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:
|
||||
@@ -1060,6 +1060,12 @@ use subs qw(mtp008_detect lm78_detect lm78_isa_detect lm78_alias_detect
|
||||
i2c_addrs => [0x18..0x1a,0x29..0x2b,0x4c..0x4e],
|
||||
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",
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
################
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user