mirror of
https://github.com/lm-sensors/lm-sensors.git
synced 2026-10-16 10:49:31 +00:00
VRM for all drivers. Same was done it Linux 2.6 some times ago. Also comment out all "set vrm" lines in sensors.conf.eg, as Linux 2.6 now accurately deduces the VRM from the CPU model. git-svn-id: http://lm-sensors.org/svn/lm-sensors/trunk@3190 7894878c-1315-0410-8ee3-d5d059ff63e0
1018 lines
30 KiB
C
1018 lines
30 KiB
C
/*
|
|
asb100.c - Part of lm_sensors, Linux kernel modules for hardware
|
|
monitoring
|
|
|
|
Copyright (c) 2003 Mark M. Hoffman <mhoffman@lightlink.com>
|
|
|
|
(derived from w83781d.c)
|
|
|
|
Copyright (c) 1998 - 2003 Frodo Looijaard <frodol@dds.nl>,
|
|
Philip Edelbrock <phil@netroedge.com>, and
|
|
Mark Studebaker <mdsxyz123@yahoo.com>
|
|
|
|
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.
|
|
*/
|
|
|
|
/*
|
|
This driver supports the hardware sensor chips: Asus ASB100 and
|
|
ASB100-A "BACH".
|
|
|
|
ASB100-A supports pwm1, while plain ASB100 does not. There is no known
|
|
way for the driver to tell which one is there.
|
|
|
|
Chip #vin #fanin #pwm #temp wchipid vendid i2c ISA
|
|
asb100 7 3 1 4 0x31 0x0694 yes no
|
|
*/
|
|
|
|
//#define DEBUG 1
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/i2c-proc.h>
|
|
#include <linux/init.h>
|
|
#include "version.h"
|
|
#include "sensors_vid.h"
|
|
#include "lm75.h"
|
|
|
|
#ifndef I2C_DRIVERID_ASB100
|
|
#define I2C_DRIVERID_ASB100 1043
|
|
#endif
|
|
|
|
/* I2C addresses to scan */
|
|
static unsigned short normal_i2c[] = { 0x2d, SENSORS_I2C_END };
|
|
static unsigned short normal_i2c_range[] = { SENSORS_I2C_END };
|
|
|
|
/* ISA addresses to scan (none) */
|
|
static unsigned int normal_isa[] = { SENSORS_ISA_END };
|
|
static unsigned int normal_isa_range[] = { SENSORS_ISA_END };
|
|
|
|
/* default VRM to 9.0 */
|
|
#define ASB100_DEFAULT_VRM 90
|
|
|
|
/* Insmod parameters */
|
|
SENSORS_INSMOD_1(asb100);
|
|
SENSORS_MODULE_PARM(force_subclients, "List of subclient addresses: " \
|
|
"{bus, clientaddr, subclientaddr1, subclientaddr2}");
|
|
|
|
/* Voltage IN registers 0-6 */
|
|
#define ASB100_REG_IN(nr) (0x20 + (nr))
|
|
#define ASB100_REG_IN_MAX(nr) (0x2b + (nr * 2))
|
|
#define ASB100_REG_IN_MIN(nr) (0x2c + (nr * 2))
|
|
|
|
/* FAN IN registers 1-3 */
|
|
#define ASB100_REG_FAN(nr) (0x27 + (nr))
|
|
#define ASB100_REG_FAN_MIN(nr) (0x3a + (nr))
|
|
|
|
/* TEMPERATURE registers 1-4 */
|
|
static const u16 asb100_reg_temp[] = {0, 0x27, 0x150, 0x250, 0x17};
|
|
static const u16 asb100_reg_temp_max[] = {0, 0x39, 0x155, 0x255, 0x18};
|
|
static const u16 asb100_reg_temp_hyst[] = {0, 0x3a, 0x153, 0x253, 0x19};
|
|
|
|
#define ASB100_REG_TEMP(nr) (asb100_reg_temp[nr])
|
|
#define ASB100_REG_TEMP_MAX(nr) (asb100_reg_temp_max[nr])
|
|
#define ASB100_REG_TEMP_HYST(nr) (asb100_reg_temp_hyst[nr])
|
|
|
|
#define ASB100_REG_TEMP2_CONFIG 0x0152
|
|
#define ASB100_REG_TEMP3_CONFIG 0x0252
|
|
|
|
|
|
#define ASB100_REG_CONFIG 0x40
|
|
#define ASB100_REG_ALARM1 0x41
|
|
#define ASB100_REG_ALARM2 0x42
|
|
#define ASB100_REG_SMIM1 0x43
|
|
#define ASB100_REG_SMIM2 0x44
|
|
#define ASB100_REG_VID_FANDIV 0x47
|
|
#define ASB100_REG_I2C_ADDR 0x48
|
|
#define ASB100_REG_CHIPID 0x49
|
|
#define ASB100_REG_I2C_SUBADDR 0x4a
|
|
#define ASB100_REG_PIN 0x4b
|
|
#define ASB100_REG_IRQ 0x4c
|
|
#define ASB100_REG_BANK 0x4e
|
|
#define ASB100_REG_CHIPMAN 0x4f
|
|
|
|
#define ASB100_REG_WCHIPID 0x58
|
|
|
|
/* bit 7 -> enable, bits 0-3 -> duty cycle */
|
|
#define ASB100_REG_PWM1 0x59
|
|
|
|
/* CONVERSIONS
|
|
Rounding and limit checking is only done on the TO_REG variants. */
|
|
|
|
/* These constants are a guess, consistent w/ w83781d */
|
|
#define ASB100_IN_MIN ( 0)
|
|
#define ASB100_IN_MAX (408)
|
|
|
|
/* IN: 1/100 V (0V to 4.08V)
|
|
REG: 16mV/bit */
|
|
static u8 IN_TO_REG(unsigned val)
|
|
{
|
|
unsigned nval = SENSORS_LIMIT(val, ASB100_IN_MIN, ASB100_IN_MAX);
|
|
return (nval * 10 + 8) / 16;
|
|
}
|
|
|
|
static unsigned IN_FROM_REG(u8 reg)
|
|
{
|
|
return (reg * 16 + 5) / 10;
|
|
}
|
|
|
|
static u8 FAN_TO_REG(long rpm, int div)
|
|
{
|
|
if (rpm == 0)
|
|
return 255;
|
|
rpm = SENSORS_LIMIT(rpm, 1, 1000000);
|
|
return SENSORS_LIMIT((1350000 + rpm * div / 2) / (rpm * div), 1, 254);
|
|
}
|
|
|
|
static int FAN_FROM_REG(u8 val, int div)
|
|
{
|
|
return val==0 ? -1 : val==255 ? 0 : 1350000/(val*div);
|
|
}
|
|
|
|
/* These constants are a guess, consistent w/ w83781d */
|
|
#define ASB100_TEMP_MIN (-1280)
|
|
#define ASB100_TEMP_MAX ( 1270)
|
|
|
|
/* TEMP: 1/10 degrees C (-128C to +127C)
|
|
REG: 1C/bit, two's complement */
|
|
static u8 TEMP_TO_REG(int temp)
|
|
{
|
|
int ntemp = SENSORS_LIMIT(temp, ASB100_TEMP_MIN, ASB100_TEMP_MAX);
|
|
ntemp += (ntemp<0 ? -5 : 5);
|
|
return (u8)(ntemp / 10);
|
|
}
|
|
|
|
static int TEMP_FROM_REG(u8 reg)
|
|
{
|
|
return (s8)reg * 10;
|
|
}
|
|
|
|
/* PWM: 0 - 255 per sensors documentation
|
|
REG: (6.25% duty cycle per bit) */
|
|
static u8 ASB100_PWM_TO_REG(int pwm)
|
|
{
|
|
pwm = SENSORS_LIMIT(pwm, 0, 255);
|
|
return (u8)(pwm / 16);
|
|
}
|
|
|
|
static int ASB100_PWM_FROM_REG(u8 reg)
|
|
{
|
|
return reg * 16;
|
|
}
|
|
|
|
#define ALARMS_FROM_REG(val) (val)
|
|
|
|
#define DIV_FROM_REG(val) (1 << (val))
|
|
|
|
/* FAN DIV: 1, 2, 4, or 8 (defaults to 2)
|
|
REG: 0, 1, 2, or 3 (respectively) (defaults to 1) */
|
|
static u8 DIV_TO_REG(long val)
|
|
{
|
|
return val==8 ? 3 : val==4 ? 2 : val==1 ? 0 : 1;
|
|
}
|
|
|
|
/* For each registered client, we need to keep some data in memory. That
|
|
data is pointed to by client->data. The structure itself is
|
|
dynamically allocated, at the same time the client itself is allocated. */
|
|
struct asb100_data {
|
|
struct i2c_client client;
|
|
struct semaphore lock;
|
|
int sysctl_id;
|
|
enum chips type;
|
|
|
|
struct semaphore update_lock;
|
|
unsigned long last_updated; /* In jiffies */
|
|
|
|
/* array of 2 pointers to subclients */
|
|
struct i2c_client *lm75[2];
|
|
|
|
char valid; /* !=0 if following fields are valid */
|
|
u8 in[7]; /* Register value */
|
|
u8 in_max[7]; /* Register value */
|
|
u8 in_min[7]; /* Register value */
|
|
u8 fan[3]; /* Register value */
|
|
u8 fan_min[3]; /* Register value */
|
|
u16 temp[4]; /* Register value (0 and 3 are u8 only) */
|
|
u16 temp_max[4]; /* Register value (0 and 3 are u8 only) */
|
|
u16 temp_hyst[4]; /* Register value (0 and 3 are u8 only) */
|
|
u8 fan_div[3]; /* Register encoding, right justified */
|
|
u8 pwm; /* Register encoding */
|
|
u8 vid; /* Register encoding, combined */
|
|
u32 alarms; /* Register encoding, combined */
|
|
u8 vrm;
|
|
};
|
|
|
|
static int asb100_attach_adapter(struct i2c_adapter *adapter);
|
|
static int asb100_detect(struct i2c_adapter *adapter, int address,
|
|
unsigned short flags, int kind);
|
|
static int asb100_detach_client(struct i2c_client *client);
|
|
|
|
static int asb100_read_value(struct i2c_client *client, u16 reg);
|
|
static void asb100_write_value(struct i2c_client *client, u16 reg, u16 val);
|
|
static void asb100_update_client(struct i2c_client *client);
|
|
static void asb100_init_client(struct i2c_client *client);
|
|
|
|
static void asb100_in(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_fan(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_temp(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_temp_add(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_vid(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_vrm(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_alarms(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_fan_div(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
static void asb100_pwm(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results);
|
|
|
|
static struct i2c_driver asb100_driver = {
|
|
.name = "asb100",
|
|
.id = I2C_DRIVERID_ASB100,
|
|
.flags = I2C_DF_NOTIFY,
|
|
.attach_adapter = asb100_attach_adapter,
|
|
.detach_client = asb100_detach_client,
|
|
};
|
|
|
|
/* The /proc/sys entries */
|
|
/* -- SENSORS SYSCTL START -- */
|
|
|
|
#define ASB100_SYSCTL_IN0 1000 /* Volts * 100 */
|
|
#define ASB100_SYSCTL_IN1 1001
|
|
#define ASB100_SYSCTL_IN2 1002
|
|
#define ASB100_SYSCTL_IN3 1003
|
|
#define ASB100_SYSCTL_IN4 1004
|
|
#define ASB100_SYSCTL_IN5 1005
|
|
#define ASB100_SYSCTL_IN6 1006
|
|
|
|
#define ASB100_SYSCTL_FAN1 1101 /* Rotations/min */
|
|
#define ASB100_SYSCTL_FAN2 1102
|
|
#define ASB100_SYSCTL_FAN3 1103
|
|
|
|
#define ASB100_SYSCTL_TEMP1 1200 /* Degrees Celsius * 10 */
|
|
#define ASB100_SYSCTL_TEMP2 1201
|
|
#define ASB100_SYSCTL_TEMP3 1202
|
|
#define ASB100_SYSCTL_TEMP4 1203
|
|
|
|
#define ASB100_SYSCTL_VID 1300 /* Volts * 1000 */
|
|
#define ASB100_SYSCTL_VRM 1301
|
|
|
|
#define ASB100_SYSCTL_PWM1 1401 /* 0-255 => 0-100% duty cycle */
|
|
|
|
#define ASB100_SYSCTL_FAN_DIV 2000 /* 1, 2, 4 or 8 */
|
|
#define ASB100_SYSCTL_ALARMS 2001 /* bitvector */
|
|
|
|
#define ASB100_ALARM_IN0 0x0001 /* ? */
|
|
#define ASB100_ALARM_IN1 0x0002 /* ? */
|
|
#define ASB100_ALARM_IN2 0x0004
|
|
#define ASB100_ALARM_IN3 0x0008
|
|
#define ASB100_ALARM_TEMP1 0x0010
|
|
#define ASB100_ALARM_TEMP2 0x0020
|
|
#define ASB100_ALARM_FAN1 0x0040
|
|
#define ASB100_ALARM_FAN2 0x0080
|
|
#define ASB100_ALARM_IN4 0x0100
|
|
#define ASB100_ALARM_IN5 0x0200 /* ? */
|
|
#define ASB100_ALARM_IN6 0x0400 /* ? */
|
|
#define ASB100_ALARM_FAN3 0x0800
|
|
#define ASB100_ALARM_CHAS 0x1000
|
|
#define ASB100_ALARM_TEMP3 0x2000
|
|
|
|
#define ASB100_ALARM_IN7 0x10000 /* ? */
|
|
#define ASB100_ALARM_IN8 0x20000 /* ? */
|
|
|
|
/* -- SENSORS SYSCTL END -- */
|
|
|
|
/* These files are created for each detected chip. This is just a template;
|
|
though at first sight, you might think we could use a statically
|
|
allocated list, we need some way to get back to the parent - which
|
|
is done through one of the 'extra' fields which are initialized
|
|
when a new copy is allocated. */
|
|
|
|
/* no datasheet - but we did get some hints from someone who
|
|
claimed to have the datasheet */
|
|
#define ASB100_SYSCTL_IN(nr) {ASB100_SYSCTL_IN##nr, "in" #nr, NULL, 0, \
|
|
0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &asb100_in}
|
|
#define ASB100_SYSCTL_FAN(nr) {ASB100_SYSCTL_FAN##nr, "fan" #nr, NULL, 0, \
|
|
0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, &asb100_fan}
|
|
#define ASB100_SYSCTL_TEMP(nr, func) {ASB100_SYSCTL_TEMP##nr, "temp" #nr, \
|
|
NULL, 0, 0644, NULL, &i2c_proc_real, &i2c_sysctl_real, NULL, func}
|
|
static ctl_table asb100_dir_table_template[] = {
|
|
ASB100_SYSCTL_IN(0),
|
|
ASB100_SYSCTL_IN(1),
|
|
ASB100_SYSCTL_IN(2),
|
|
ASB100_SYSCTL_IN(3),
|
|
ASB100_SYSCTL_IN(4),
|
|
ASB100_SYSCTL_IN(5),
|
|
ASB100_SYSCTL_IN(6),
|
|
|
|
ASB100_SYSCTL_FAN(1),
|
|
ASB100_SYSCTL_FAN(2),
|
|
ASB100_SYSCTL_FAN(3),
|
|
|
|
ASB100_SYSCTL_TEMP(1, &asb100_temp),
|
|
ASB100_SYSCTL_TEMP(2, &asb100_temp_add),
|
|
ASB100_SYSCTL_TEMP(3, &asb100_temp_add),
|
|
ASB100_SYSCTL_TEMP(4, &asb100_temp),
|
|
|
|
{ASB100_SYSCTL_VID, "vid", NULL, 0, 0444, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &asb100_vid},
|
|
{ASB100_SYSCTL_VRM, "vrm", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &asb100_vrm},
|
|
{ASB100_SYSCTL_FAN_DIV, "fan_div", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &asb100_fan_div},
|
|
{ASB100_SYSCTL_ALARMS, "alarms", NULL, 0, 0444, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &asb100_alarms},
|
|
{ASB100_SYSCTL_PWM1, "pwm1", NULL, 0, 0644, NULL, &i2c_proc_real,
|
|
&i2c_sysctl_real, NULL, &asb100_pwm},
|
|
{0}
|
|
};
|
|
|
|
/* This function is called when:
|
|
asb100_driver is inserted (when this module is loaded), for each
|
|
available adapter
|
|
when a new adapter is inserted (and asb100_driver is still present)
|
|
*/
|
|
static int asb100_attach_adapter(struct i2c_adapter *adapter)
|
|
{
|
|
return i2c_detect(adapter, &addr_data, asb100_detect);
|
|
}
|
|
|
|
static int asb100_detect_subclients(struct i2c_adapter *adapter, int address,
|
|
int kind, struct i2c_client *new_client)
|
|
{
|
|
int i, id, err = 0;
|
|
struct asb100_data *data = new_client->data;
|
|
|
|
data->lm75[0] = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
|
|
if (!(data->lm75[0])) {
|
|
err = -ENOMEM;
|
|
goto ERROR_SC_0;
|
|
}
|
|
memset(data->lm75[0], 0x00, sizeof(struct i2c_client));
|
|
|
|
data->lm75[1] = kmalloc(sizeof(struct i2c_client), GFP_KERNEL);
|
|
if (!(data->lm75[1])) {
|
|
err = -ENOMEM;
|
|
goto ERROR_SC_1;
|
|
}
|
|
memset(data->lm75[1], 0x00, sizeof(struct i2c_client));
|
|
|
|
id = i2c_adapter_id(adapter);
|
|
|
|
if (force_subclients[0] == id && force_subclients[1] == address) {
|
|
for (i = 2; i <= 3; i++) {
|
|
if (force_subclients[i] < 0x48 ||
|
|
force_subclients[i] > 0x4f) {
|
|
printk(KERN_ERR "asb100.o: invalid subclient "
|
|
"address %d; must be 0x48-0x4f\n",
|
|
force_subclients[i]);
|
|
goto ERROR_SC_2;
|
|
}
|
|
}
|
|
asb100_write_value(new_client, ASB100_REG_I2C_SUBADDR,
|
|
(force_subclients[2] & 0x07) |
|
|
((force_subclients[3] & 0x07) <<4));
|
|
data->lm75[0]->addr = force_subclients[2];
|
|
data->lm75[1]->addr = force_subclients[3];
|
|
} else {
|
|
int val = asb100_read_value(new_client, ASB100_REG_I2C_SUBADDR);
|
|
data->lm75[0]->addr = 0x48 + (val & 0x07);
|
|
data->lm75[1]->addr = 0x48 + ((val >> 4) & 0x07);
|
|
}
|
|
|
|
if(data->lm75[0]->addr == data->lm75[1]->addr) {
|
|
printk(KERN_ERR "asb100.o: duplicate addresses 0x%x "
|
|
"for subclients\n", data->lm75[0]->addr);
|
|
goto ERROR_SC_2;
|
|
}
|
|
|
|
for (i = 0; i <= 1; i++) {
|
|
data->lm75[i]->data = NULL;
|
|
data->lm75[i]->adapter = adapter;
|
|
data->lm75[i]->driver = &asb100_driver;
|
|
data->lm75[i]->flags = 0;
|
|
strcpy(data->lm75[i]->name, "asb100 subclient");
|
|
}
|
|
|
|
if ((err = i2c_attach_client(data->lm75[0]))) {
|
|
printk(KERN_ERR "asb100.o: Subclient %d registration "
|
|
"at address 0x%x failed.\n", i, data->lm75[0]->addr);
|
|
goto ERROR_SC_2;
|
|
}
|
|
|
|
if ((err = i2c_attach_client(data->lm75[1]))) {
|
|
printk(KERN_ERR "asb100.o: Subclient %d registration "
|
|
"at address 0x%x failed.\n", i, data->lm75[1]->addr);
|
|
goto ERROR_SC_3;
|
|
}
|
|
|
|
return 0;
|
|
|
|
/* Undo inits in case of errors */
|
|
ERROR_SC_3:
|
|
i2c_detach_client(data->lm75[0]);
|
|
ERROR_SC_2:
|
|
kfree(data->lm75[1]);
|
|
ERROR_SC_1:
|
|
kfree(data->lm75[0]);
|
|
ERROR_SC_0:
|
|
return err;
|
|
}
|
|
|
|
static int asb100_detect(struct i2c_adapter *adapter, int address,
|
|
unsigned short flags, int kind)
|
|
{
|
|
int err = 0;
|
|
struct i2c_client *new_client;
|
|
struct asb100_data *data;
|
|
|
|
/* asb100 is SMBus only */
|
|
if (i2c_is_isa_adapter(adapter)) {
|
|
pr_debug("asb100.o: detect failed, "
|
|
"cannot attach to legacy adapter!\n");
|
|
goto ERROR0;
|
|
}
|
|
|
|
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
|
pr_debug("asb100.o: detect failed, "
|
|
"smbus byte data not supported!\n");
|
|
goto ERROR0;
|
|
}
|
|
|
|
/* OK. For now, we presume we have a valid client. We now create the
|
|
client structure, even though we cannot fill it completely yet.
|
|
But it allows us to access asb100_{read,write}_value. */
|
|
|
|
if (!(data = kmalloc(sizeof(struct asb100_data), GFP_KERNEL))) {
|
|
pr_debug("asb100.o: detect failed, kmalloc failed!\n");
|
|
err = -ENOMEM;
|
|
goto ERROR0;
|
|
}
|
|
|
|
new_client = &data->client;
|
|
new_client->addr = address;
|
|
init_MUTEX(&data->lock);
|
|
new_client->data = data;
|
|
new_client->adapter = adapter;
|
|
new_client->driver = &asb100_driver;
|
|
new_client->flags = 0;
|
|
|
|
/* Now, we do the remaining detection. */
|
|
|
|
/* The chip may be stuck in some other bank than bank 0. This may
|
|
make reading other information impossible. Specify a force=... or
|
|
force_*=... parameter, and the chip will be reset to the right
|
|
bank. */
|
|
if (kind < 0) {
|
|
|
|
int val1 = asb100_read_value(new_client, ASB100_REG_BANK);
|
|
int val2 = asb100_read_value(new_client, ASB100_REG_CHIPMAN);
|
|
|
|
/* If we're in bank 0 */
|
|
if ( (!(val1 & 0x07)) &&
|
|
/* Check for ASB100 ID (low byte) */
|
|
( ((!(val1 & 0x80)) && (val2 != 0x94)) ||
|
|
/* Check for ASB100 ID (high byte ) */
|
|
((val1 & 0x80) && (val2 != 0x06)) ) ) {
|
|
pr_debug("asb100.o: detect failed, "
|
|
"bad chip id 0x%02x!\n", val2);
|
|
goto ERROR1;
|
|
}
|
|
|
|
} /* kind < 0 */
|
|
|
|
/* We have either had a force parameter, or we have already detected
|
|
Winbond. Put it now into bank 0 and Vendor ID High Byte */
|
|
asb100_write_value(new_client, ASB100_REG_BANK,
|
|
(asb100_read_value(new_client, ASB100_REG_BANK) & 0x78) | 0x80);
|
|
|
|
/* Determine the chip type. */
|
|
if (kind <= 0) {
|
|
int val1 = asb100_read_value(new_client, ASB100_REG_WCHIPID);
|
|
int val2 = asb100_read_value(new_client, ASB100_REG_CHIPMAN);
|
|
|
|
if ((val1 == 0x31) && (val2 == 0x06))
|
|
kind = asb100;
|
|
else {
|
|
if (kind == 0)
|
|
printk (KERN_WARNING "asb100.o: Ignoring "
|
|
"'force' parameter for unknown chip "
|
|
"at adapter %d, address 0x%02x.\n",
|
|
i2c_adapter_id(adapter), address);
|
|
goto ERROR1;
|
|
}
|
|
}
|
|
|
|
/* Fill in remaining client fields and put it into the global list */
|
|
strcpy(new_client->name, "ASB100 chip");
|
|
data->type = kind;
|
|
|
|
data->valid = 0;
|
|
init_MUTEX(&data->update_lock);
|
|
|
|
/* Tell the I2C layer a new client has arrived */
|
|
if ((err = i2c_attach_client(new_client)))
|
|
goto ERROR1;
|
|
|
|
/* Attach secondary lm75 clients */
|
|
if ((err = asb100_detect_subclients(adapter, address, kind,
|
|
new_client)))
|
|
goto ERROR2;
|
|
|
|
/* Initialize the chip */
|
|
asb100_init_client(new_client);
|
|
|
|
/* Register a new directory entry with module sensors */
|
|
if ((data->sysctl_id = i2c_register_entry(new_client, "asb100",
|
|
asb100_dir_table_template, THIS_MODULE)) < 0) {
|
|
err = data->sysctl_id;
|
|
goto ERROR3;
|
|
}
|
|
|
|
return 0;
|
|
|
|
ERROR3:
|
|
i2c_detach_client(data->lm75[0]);
|
|
kfree(data->lm75[1]);
|
|
kfree(data->lm75[0]);
|
|
ERROR2:
|
|
i2c_detach_client(new_client);
|
|
ERROR1:
|
|
kfree(data);
|
|
ERROR0:
|
|
return err;
|
|
}
|
|
|
|
static int asb100_detach_client(struct i2c_client *client)
|
|
{
|
|
int err;
|
|
struct asb100_data *data = client->data;
|
|
|
|
/* remove sysctl table (primary client only) */
|
|
if ((data))
|
|
i2c_deregister_entry(data->sysctl_id);
|
|
|
|
if ((err = i2c_detach_client(client))) {
|
|
printk (KERN_ERR "asb100.o: Client deregistration failed; "
|
|
"client not detached.\n");
|
|
return err;
|
|
}
|
|
|
|
if (data) {
|
|
/* primary client */
|
|
kfree(data);
|
|
} else {
|
|
/* subclients */
|
|
kfree(client);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* The SMBus locks itself, usually, but nothing may access the Winbond between
|
|
bank switches. ISA access must always be locked explicitly!
|
|
We ignore the W83781D BUSY flag at this moment - it could lead to deadlocks,
|
|
would slow down the W83781D access and should not be necessary.
|
|
There are some ugly typecasts here, but the good news is - they should
|
|
nowhere else be necessary! */
|
|
static int asb100_read_value(struct i2c_client *client, u16 reg)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
struct i2c_client *cl;
|
|
int res, bank;
|
|
|
|
down(&data->lock);
|
|
|
|
bank = (reg >> 8) & 0x0f;
|
|
if (bank > 2)
|
|
/* switch banks */
|
|
i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank);
|
|
|
|
if (bank == 0 || bank > 2) {
|
|
res = i2c_smbus_read_byte_data(client, reg & 0xff);
|
|
} else {
|
|
/* switch to subclient */
|
|
cl = data->lm75[bank - 1];
|
|
|
|
/* convert from ISA to LM75 I2C addresses */
|
|
switch (reg & 0xff) {
|
|
case 0x50: /* TEMP */
|
|
res = swab16(i2c_smbus_read_word_data (cl, 0));
|
|
break;
|
|
case 0x52: /* CONFIG */
|
|
res = i2c_smbus_read_byte_data(cl, 1);
|
|
break;
|
|
case 0x53: /* HYST */
|
|
res = swab16(i2c_smbus_read_word_data (cl, 2));
|
|
break;
|
|
case 0x55: /* MAX */
|
|
default:
|
|
res = swab16(i2c_smbus_read_word_data (cl, 3));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bank > 2)
|
|
i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0);
|
|
|
|
up(&data->lock);
|
|
|
|
return res;
|
|
}
|
|
|
|
static void asb100_write_value(struct i2c_client *client, u16 reg, u16 value)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
struct i2c_client *cl;
|
|
int bank;
|
|
|
|
down(&data->lock);
|
|
|
|
bank = (reg >> 8) & 0x0f;
|
|
if (bank > 2)
|
|
/* switch banks */
|
|
i2c_smbus_write_byte_data(client, ASB100_REG_BANK, bank);
|
|
|
|
if (bank == 0 || bank > 2) {
|
|
i2c_smbus_write_byte_data(client, reg & 0xff, value & 0xff);
|
|
} else {
|
|
/* switch to subclient */
|
|
cl = data->lm75[bank - 1];
|
|
|
|
/* convert from ISA to LM75 I2C addresses */
|
|
switch (reg & 0xff) {
|
|
case 0x52: /* CONFIG */
|
|
i2c_smbus_write_byte_data(cl, 1, value & 0xff);
|
|
break;
|
|
case 0x53: /* HYST */
|
|
i2c_smbus_write_word_data(cl, 2, swab16(value));
|
|
break;
|
|
case 0x55: /* MAX */
|
|
i2c_smbus_write_word_data(cl, 3, swab16(value));
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bank > 2)
|
|
i2c_smbus_write_byte_data(client, ASB100_REG_BANK, 0);
|
|
|
|
up(&data->lock);
|
|
}
|
|
|
|
static void asb100_init_client(struct i2c_client *client)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int vid = 0;
|
|
|
|
vid = asb100_read_value(client, ASB100_REG_VID_FANDIV) & 0x0f;
|
|
vid |= (asb100_read_value(client, ASB100_REG_CHIPID) & 0x01) << 4;
|
|
data->vrm = ASB100_DEFAULT_VRM;
|
|
vid = vid_from_reg(vid, data->vrm);
|
|
|
|
/* Start monitoring */
|
|
asb100_write_value(client, ASB100_REG_CONFIG,
|
|
(asb100_read_value(client, ASB100_REG_CONFIG) & 0xf7) | 0x01);
|
|
}
|
|
|
|
static void asb100_update_client(struct i2c_client *client)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int i;
|
|
|
|
down(&data->update_lock);
|
|
|
|
if (time_after(jiffies - data->last_updated, HZ + HZ / 2) ||
|
|
time_before(jiffies, data->last_updated) || !data->valid) {
|
|
|
|
pr_debug("asb100.o: starting device update...\n");
|
|
|
|
/* 7 voltage inputs */
|
|
for (i = 0; i < 7; i++) {
|
|
data->in[i] = asb100_read_value(client,
|
|
ASB100_REG_IN(i));
|
|
data->in_min[i] = asb100_read_value(client,
|
|
ASB100_REG_IN_MIN(i));
|
|
data->in_max[i] = asb100_read_value(client,
|
|
ASB100_REG_IN_MAX(i));
|
|
}
|
|
|
|
/* 3 fan inputs */
|
|
for (i = 1; i <= 3; i++) {
|
|
data->fan[i-1] = asb100_read_value(client,
|
|
ASB100_REG_FAN(i));
|
|
data->fan_min[i-1] = asb100_read_value(client,
|
|
ASB100_REG_FAN_MIN(i));
|
|
}
|
|
|
|
/* 4 temperature inputs */
|
|
for (i = 1; i <= 4; i++) {
|
|
data->temp[i-1] = asb100_read_value(client,
|
|
ASB100_REG_TEMP(i));
|
|
data->temp_max[i-1] = asb100_read_value(client,
|
|
ASB100_REG_TEMP_MAX(i));
|
|
data->temp_hyst[i-1] = asb100_read_value(client,
|
|
ASB100_REG_TEMP_HYST(i));
|
|
}
|
|
|
|
/* VID and fan divisors */
|
|
i = asb100_read_value(client, ASB100_REG_VID_FANDIV);
|
|
data->vid = i & 0x0f;
|
|
data->vid |= (asb100_read_value(client,
|
|
ASB100_REG_CHIPID) & 0x01) << 4;
|
|
data->fan_div[0] = (i >> 4) & 0x03;
|
|
data->fan_div[1] = (i >> 6) & 0x03;
|
|
data->fan_div[2] = (asb100_read_value(client,
|
|
ASB100_REG_PIN) >> 6) & 0x03;
|
|
|
|
/* PWM */
|
|
data->pwm = asb100_read_value(client, ASB100_REG_PWM1);
|
|
|
|
/* alarms */
|
|
data->alarms = asb100_read_value(client, ASB100_REG_ALARM1) +
|
|
(asb100_read_value(client, ASB100_REG_ALARM2) << 8);
|
|
|
|
data->last_updated = jiffies;
|
|
data->valid = 1;
|
|
|
|
pr_debug("asb100.o: ... update complete.\n");
|
|
}
|
|
|
|
up(&data->update_lock);
|
|
}
|
|
|
|
|
|
/* The next few functions are the call-back functions of the /proc/sys and
|
|
sysctl files. Which function is used is defined in the ctl_table in
|
|
the extra1 field.
|
|
Each function must return the magnitude (power of 10 to divide the date
|
|
with) if it is called with operation==SENSORS_PROC_REAL_INFO. It must
|
|
put a maximum of *nrels elements in results reflecting the data of this
|
|
file, and set *nrels to the number it actually put in it, if operation==
|
|
SENSORS_PROC_REAL_READ. Finally, it must get upto *nrels elements from
|
|
results and write them to the chip, if operations==SENSORS_PROC_REAL_WRITE.
|
|
Note that on SENSORS_PROC_REAL_READ, I do not check whether results is
|
|
large enough (by checking the incoming value of *nrels). This is not very
|
|
good practice, but as long as you put less than about 5 values in results,
|
|
you can assume it is large enough. */
|
|
static void asb100_in(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int nr = ctl_name - ASB100_SYSCTL_IN0;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 2;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = IN_FROM_REG(data->in_min[nr]);
|
|
results[1] = IN_FROM_REG(data->in_max[nr]);
|
|
results[2] = IN_FROM_REG(data->in[nr]);
|
|
*nrels_mag = 3;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->in_min[nr] = IN_TO_REG(results[0]);
|
|
asb100_write_value(client, ASB100_REG_IN_MIN(nr),
|
|
data->in_min[nr]);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->in_max[nr] = IN_TO_REG(results[1]);
|
|
asb100_write_value(client, ASB100_REG_IN_MAX(nr),
|
|
data->in_max[nr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void asb100_fan(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int nr = ctl_name - ASB100_SYSCTL_FAN1 + 1;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = FAN_FROM_REG(data->fan_min[nr - 1],
|
|
DIV_FROM_REG(data->fan_div[nr - 1]));
|
|
results[1] = FAN_FROM_REG(data->fan[nr - 1],
|
|
DIV_FROM_REG(data->fan_div[nr - 1]));
|
|
*nrels_mag = 2;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->fan_min[nr - 1] =
|
|
FAN_TO_REG(results[0],
|
|
DIV_FROM_REG(data->fan_div[nr-1]));
|
|
asb100_write_value(client,
|
|
ASB100_REG_FAN_MIN(nr),
|
|
data->fan_min[nr - 1]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void asb100_temp(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int nr = ctl_name - ASB100_SYSCTL_TEMP1;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 1;
|
|
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = TEMP_FROM_REG(data->temp_max[nr]);
|
|
results[1] = TEMP_FROM_REG(data->temp_hyst[nr]);
|
|
results[2] = TEMP_FROM_REG(data->temp[nr]);
|
|
*nrels_mag = 3;
|
|
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->temp_max[nr] = TEMP_TO_REG(results[0]);
|
|
asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1),
|
|
data->temp_max[nr]);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->temp_hyst[nr] = TEMP_TO_REG(results[1]);
|
|
asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1),
|
|
data->temp_hyst[nr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void asb100_temp_add(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int nr = ctl_name - ASB100_SYSCTL_TEMP1;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 1;
|
|
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
|
|
results[0] = LM75_TEMP_FROM_REG(data->temp_max[nr]);
|
|
results[1] = LM75_TEMP_FROM_REG(data->temp_hyst[nr]);
|
|
results[2] = LM75_TEMP_FROM_REG(data->temp[nr]);
|
|
*nrels_mag = 3;
|
|
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1) {
|
|
data->temp_max[nr] =
|
|
LM75_TEMP_TO_REG(results[0]);
|
|
asb100_write_value(client, ASB100_REG_TEMP_MAX(nr+1),
|
|
data->temp_max[nr]);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->temp_hyst[nr] =
|
|
LM75_TEMP_TO_REG(results[1]);
|
|
asb100_write_value(client, ASB100_REG_TEMP_HYST(nr+1),
|
|
data->temp_hyst[nr]);
|
|
}
|
|
}
|
|
}
|
|
|
|
void asb100_vid(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 3;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = vid_from_reg(data->vid, data->vrm);
|
|
*nrels_mag = 1;
|
|
}
|
|
}
|
|
|
|
void asb100_vrm(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 1;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
results[0] = data->vrm;
|
|
*nrels_mag = 1;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
if (*nrels_mag >= 1)
|
|
data->vrm = results[0];
|
|
}
|
|
}
|
|
|
|
void asb100_alarms(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = ALARMS_FROM_REG(data->alarms);
|
|
*nrels_mag = 1;
|
|
}
|
|
}
|
|
|
|
void asb100_fan_div(struct i2c_client *client, int operation,
|
|
int ctl_name, int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
int old, old2;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = DIV_FROM_REG(data->fan_div[0]);
|
|
results[1] = DIV_FROM_REG(data->fan_div[1]);
|
|
results[2] = DIV_FROM_REG(data->fan_div[2]);
|
|
*nrels_mag = 3;
|
|
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
old = asb100_read_value(client, ASB100_REG_VID_FANDIV);
|
|
if (*nrels_mag >= 3) {
|
|
data->fan_div[2] = DIV_TO_REG(results[2]);
|
|
old2 = asb100_read_value(client, ASB100_REG_PIN);
|
|
old2 = (old2 & 0x3f) | ((data->fan_div[2] & 0x03) << 6);
|
|
asb100_write_value(client, ASB100_REG_PIN, old2);
|
|
}
|
|
if (*nrels_mag >= 2) {
|
|
data->fan_div[1] = DIV_TO_REG(results[1]);
|
|
old = (old & 0x3f) | ((data->fan_div[1] & 0x03) << 6);
|
|
}
|
|
if (*nrels_mag >= 1) {
|
|
data->fan_div[0] = DIV_TO_REG(results[0]);
|
|
old = (old & 0xcf) | ((data->fan_div[0] & 0x03) << 4);
|
|
asb100_write_value(client, ASB100_REG_VID_FANDIV, old);
|
|
}
|
|
}
|
|
}
|
|
|
|
void asb100_pwm(struct i2c_client *client, int operation, int ctl_name,
|
|
int *nrels_mag, long *results)
|
|
{
|
|
struct asb100_data *data = client->data;
|
|
|
|
if (operation == SENSORS_PROC_REAL_INFO)
|
|
*nrels_mag = 0;
|
|
else if (operation == SENSORS_PROC_REAL_READ) {
|
|
asb100_update_client(client);
|
|
results[0] = ASB100_PWM_FROM_REG(data->pwm & 0x0f);
|
|
results[1] = (data->pwm & 0x80) ? 1 : 0;
|
|
*nrels_mag = 2;
|
|
} else if (operation == SENSORS_PROC_REAL_WRITE) {
|
|
u8 val = data->pwm;
|
|
if (*nrels_mag >= 1) {
|
|
val = 0x0f & ASB100_PWM_TO_REG(results[0]);
|
|
if (*nrels_mag >= 2) {
|
|
if (results[1])
|
|
val |= 0x80;
|
|
else
|
|
val &= ~0x80;
|
|
}
|
|
asb100_write_value(client, ASB100_REG_PWM1, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int __init asb100_init(void)
|
|
{
|
|
printk(KERN_INFO "asb100.o version %s (%s)\n", LM_VERSION, LM_DATE);
|
|
return i2c_add_driver(&asb100_driver);
|
|
}
|
|
|
|
static void __exit asb100_exit(void)
|
|
{
|
|
i2c_del_driver(&asb100_driver);
|
|
}
|
|
|
|
MODULE_AUTHOR( "Mark M. Hoffman <mhoffman@lightlink.com>, "
|
|
"Frodo Looijaard <frodol@dds.nl>, "
|
|
"Philip Edelbrock <phil@netroedge.com>, and "
|
|
"Mark Studebaker <mdsxyz123@yahoo.com>");
|
|
|
|
MODULE_DESCRIPTION("ASB100 'Bach' driver");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
module_init(asb100_init);
|
|
module_exit(asb100_exit);
|
|
|