adm9240.c

来自「linux 内核源代码」· C语言 代码 · 共 784 行 · 第 1/2 页

C
784
字号
/* * adm9240.c	Part of lm_sensors, Linux kernel modules for hardware * 		monitoring * * Copyright (C) 1999	Frodo Looijaard <frodol@dds.nl> *			Philip Edelbrock <phil@netroedge.com> * Copyright (C) 2003	Michiel Rook <michiel@grendelproject.nl> * Copyright (C) 2005	Grant Coady <gcoady.lk@gmail.com> with valuable * 				guidance from Jean Delvare * * Driver supports	Analog Devices		ADM9240 *			Dallas Semiconductor	DS1780 *			National Semiconductor	LM81 * * ADM9240 is the reference, DS1780 and LM81 are register compatibles * * Voltage	Six inputs are scaled by chip, VID also reported * Temperature	Chip temperature to 0.5'C, maximum and max_hysteris * Fans		2 fans, low speed alarm, automatic fan clock divider * Alarms	16-bit map of active alarms * Analog Out	0..1250 mV output * * Chassis Intrusion: clear CI latch with 'echo 1 > chassis_clear' * * Test hardware: Intel SE440BX-2 desktop motherboard --Grant * * LM81 extended temp reading not implemented * * 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/init.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/i2c.h>#include <linux/hwmon-sysfs.h>#include <linux/hwmon.h>#include <linux/hwmon-vid.h>#include <linux/err.h>#include <linux/mutex.h>/* Addresses to scan */static unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f,					I2C_CLIENT_END };/* Insmod parameters */I2C_CLIENT_INSMOD_3(adm9240, ds1780, lm81);/* ADM9240 registers */#define ADM9240_REG_MAN_ID		0x3e#define ADM9240_REG_DIE_REV		0x3f#define ADM9240_REG_CONFIG		0x40#define ADM9240_REG_IN(nr)		(0x20 + (nr))   /* 0..5 */#define ADM9240_REG_IN_MAX(nr)		(0x2b + (nr) * 2)#define ADM9240_REG_IN_MIN(nr)		(0x2c + (nr) * 2)#define ADM9240_REG_FAN(nr)		(0x28 + (nr))   /* 0..1 */#define ADM9240_REG_FAN_MIN(nr)		(0x3b + (nr))#define ADM9240_REG_INT(nr)		(0x41 + (nr))#define ADM9240_REG_INT_MASK(nr)	(0x43 + (nr))#define ADM9240_REG_TEMP		0x27#define ADM9240_REG_TEMP_MAX(nr)	(0x39 + (nr)) /* 0, 1 = high, hyst */#define ADM9240_REG_ANALOG_OUT		0x19#define ADM9240_REG_CHASSIS_CLEAR	0x46#define ADM9240_REG_VID_FAN_DIV		0x47#define ADM9240_REG_I2C_ADDR		0x48#define ADM9240_REG_VID4		0x49#define ADM9240_REG_TEMP_CONF		0x4b/* generalised scaling with integer rounding */static inline int SCALE(long val, int mul, int div){	if (val < 0)		return (val * mul - div / 2) / div;	else		return (val * mul + div / 2) / div;}/* adm9240 internally scales voltage measurements */static const u16 nom_mv[] = { 2500, 2700, 3300, 5000, 12000, 2700 };static inline unsigned int IN_FROM_REG(u8 reg, int n){	return SCALE(reg, nom_mv[n], 192);}static inline u8 IN_TO_REG(unsigned long val, int n){	return SENSORS_LIMIT(SCALE(val, 192, nom_mv[n]), 0, 255);}/* temperature range: -40..125, 127 disables temperature alarm */static inline s8 TEMP_TO_REG(long val){	return SENSORS_LIMIT(SCALE(val, 1, 1000), -40, 127);}/* two fans, each with low fan speed limit */static inline unsigned int FAN_FROM_REG(u8 reg, u8 div){	if (!reg) /* error */		return -1;	if (reg == 255)		return 0;	return SCALE(1350000, 1, reg * div);}/* analog out 0..1250mV */static inline u8 AOUT_TO_REG(unsigned long val){	return SENSORS_LIMIT(SCALE(val, 255, 1250), 0, 255);}static inline unsigned int AOUT_FROM_REG(u8 reg){	return SCALE(reg, 1250, 255);}static int adm9240_attach_adapter(struct i2c_adapter *adapter);static int adm9240_detect(struct i2c_adapter *adapter, int address, int kind);static void adm9240_init_client(struct i2c_client *client);static int adm9240_detach_client(struct i2c_client *client);static struct adm9240_data *adm9240_update_device(struct device *dev);/* driver data */static struct i2c_driver adm9240_driver = {	.driver = {		.name	= "adm9240",	},	.id		= I2C_DRIVERID_ADM9240,	.attach_adapter	= adm9240_attach_adapter,	.detach_client	= adm9240_detach_client,};/* per client data */struct adm9240_data {	enum chips type;	struct i2c_client client;	struct device *hwmon_dev;	struct mutex update_lock;	char valid;	unsigned long last_updated_measure;	unsigned long last_updated_config;	u8 in[6];		/* ro	in0_input */	u8 in_max[6];		/* rw	in0_max */	u8 in_min[6];		/* rw	in0_min */	u8 fan[2];		/* ro	fan1_input */	u8 fan_min[2];		/* rw	fan1_min */	u8 fan_div[2];		/* rw	fan1_div, read-only accessor */	s16 temp;		/* ro	temp1_input, 9-bit sign-extended */	s8 temp_max[2];		/* rw	0 -> temp_max, 1 -> temp_max_hyst */	u16 alarms;		/* ro	alarms */	u8 aout;		/* rw	aout_output */	u8 vid;			/* ro	vid */	u8 vrm;			/* --	vrm set on startup, no accessor */};/*** sysfs accessors ***//* temperature */static ssize_t show_temp(struct device *dev, struct device_attribute *dummy,		char *buf){	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", data->temp * 500); /* 9-bit value */}static ssize_t show_max(struct device *dev, struct device_attribute *devattr,		char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", data->temp_max[attr->index] * 1000);}static ssize_t set_max(struct device *dev, struct device_attribute *devattr,		const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct adm9240_data *data = i2c_get_clientdata(client);	long val = simple_strtol(buf, NULL, 10);	mutex_lock(&data->update_lock);	data->temp_max[attr->index] = TEMP_TO_REG(val);	i2c_smbus_write_byte_data(client, ADM9240_REG_TEMP_MAX(attr->index),			data->temp_max[attr->index]);	mutex_unlock(&data->update_lock);	return count;}static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp, NULL);static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO,		show_max, set_max, 0);static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO,		show_max, set_max, 1);/* voltage */static ssize_t show_in(struct device *dev, struct device_attribute *devattr,		char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", IN_FROM_REG(data->in[attr->index],				attr->index));}static ssize_t show_in_min(struct device *dev,		struct device_attribute *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", IN_FROM_REG(data->in_min[attr->index],				attr->index));}static ssize_t show_in_max(struct device *dev,		struct device_attribute *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", IN_FROM_REG(data->in_max[attr->index],				attr->index));}static ssize_t set_in_min(struct device *dev,		struct device_attribute *devattr,		const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct adm9240_data *data = i2c_get_clientdata(client);	unsigned long val = simple_strtoul(buf, NULL, 10);	mutex_lock(&data->update_lock);	data->in_min[attr->index] = IN_TO_REG(val, attr->index);	i2c_smbus_write_byte_data(client, ADM9240_REG_IN_MIN(attr->index),			data->in_min[attr->index]);	mutex_unlock(&data->update_lock);	return count;}static ssize_t set_in_max(struct device *dev,		struct device_attribute *devattr,		const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct adm9240_data *data = i2c_get_clientdata(client);	unsigned long val = simple_strtoul(buf, NULL, 10);	mutex_lock(&data->update_lock);	data->in_max[attr->index] = IN_TO_REG(val, attr->index);	i2c_smbus_write_byte_data(client, ADM9240_REG_IN_MAX(attr->index),			data->in_max[attr->index]);	mutex_unlock(&data->update_lock);	return count;}#define vin(nr)							\static SENSOR_DEVICE_ATTR(in##nr##_input, S_IRUGO, 		\		show_in, NULL, nr);				\static SENSOR_DEVICE_ATTR(in##nr##_min, S_IRUGO | S_IWUSR,	\		show_in_min, set_in_min, nr);			\static SENSOR_DEVICE_ATTR(in##nr##_max, S_IRUGO | S_IWUSR,	\		show_in_max, set_in_max, nr);vin(0);vin(1);vin(2);vin(3);vin(4);vin(5);/* fans */static ssize_t show_fan(struct device *dev,		struct device_attribute *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index],				1 << data->fan_div[attr->index]));}static ssize_t show_fan_min(struct device *dev,		struct device_attribute *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan_min[attr->index],				1 << data->fan_div[attr->index]));}static ssize_t show_fan_div(struct device *dev,		struct device_attribute *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct adm9240_data *data = adm9240_update_device(dev);	return sprintf(buf, "%d\n", 1 << data->fan_div[attr->index]);}/* write new fan div, callers must hold data->update_lock */static void adm9240_write_fan_div(struct i2c_client *client, int nr,		u8 fan_div){	u8 reg, old, shift = (nr + 2) * 2;	reg = i2c_smbus_read_byte_data(client, ADM9240_REG_VID_FAN_DIV);	old = (reg >> shift) & 3;	reg &= ~(3 << shift);	reg |= (fan_div << shift);	i2c_smbus_write_byte_data(client, ADM9240_REG_VID_FAN_DIV, reg);	dev_dbg(&client->dev, "fan%d clock divider changed from %u "			"to %u\n", nr + 1, 1 << old, 1 << fan_div);}/* * set fan speed low limit: * * - value is zero: disable fan speed low limit alarm * * - value is below fan speed measurement range: enable fan speed low *   limit alarm to be asserted while fan speed too slow to measure * * - otherwise: select fan clock divider to suit fan speed low limit, *   measurement code may adjust registers to ensure fan speed reading */static ssize_t set_fan_min(struct device *dev,		struct device_attribute *devattr,		const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct i2c_client *client = to_i2c_client(dev);	struct adm9240_data *data = i2c_get_clientdata(client);	unsigned long val = simple_strtoul(buf, NULL, 10);	int nr = attr->index;	u8 new_div;	mutex_lock(&data->update_lock);	if (!val) {		data->fan_min[nr] = 255;		new_div = data->fan_div[nr];		dev_dbg(&client->dev, "fan%u low limit set disabled\n",				nr + 1);	} else if (val < 1350000 / (8 * 254)) {		new_div = 3;		data->fan_min[nr] = 254;		dev_dbg(&client->dev, "fan%u low limit set minimum %u\n",				nr + 1, FAN_FROM_REG(254, 1 << new_div));	} else {		unsigned int new_min = 1350000 / val;		new_div = 0;		while (new_min > 192 && new_div < 3) {			new_div++;			new_min /= 2;		}		if (!new_min) /* keep > 0 */			new_min++;		data->fan_min[nr] = new_min;		dev_dbg(&client->dev, "fan%u low limit set fan speed %u\n",				nr + 1, FAN_FROM_REG(new_min, 1 << new_div));	}	if (new_div != data->fan_div[nr]) {		data->fan_div[nr] = new_div;		adm9240_write_fan_div(client, nr, new_div);	}	i2c_smbus_write_byte_data(client, ADM9240_REG_FAN_MIN(nr),			data->fan_min[nr]);

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?