smsc47m1.c

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

C
759
字号
/*    smsc47m1.c - Part of lm_sensors, Linux kernel modules                 for hardware monitoring    Supports the SMSC LPC47B27x, LPC47M10x, LPC47M112, LPC47M13x,    LPC47M14x, LPC47M15x, LPC47M192, LPC47M292 and LPC47M997    Super-I/O chips.    Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com>    Copyright (C) 2004-2007 Jean Delvare <khali@linux-fr.org>    Ported to Linux 2.6 by Gabriele Gorla <gorlik@yahoo.com>                        and Jean Delvare    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/ioport.h>#include <linux/jiffies.h>#include <linux/platform_device.h>#include <linux/hwmon.h>#include <linux/hwmon-sysfs.h>#include <linux/err.h>#include <linux/init.h>#include <linux/mutex.h>#include <linux/sysfs.h>#include <asm/io.h>static struct platform_device *pdev;#define DRVNAME "smsc47m1"enum chips { smsc47m1, smsc47m2 };/* Super-I/0 registers and commands */#define	REG	0x2e	/* The register to read/write */#define	VAL	0x2f	/* The value to read/write */static inline voidsuperio_outb(int reg, int val){	outb(reg, REG);	outb(val, VAL);}static inline intsuperio_inb(int reg){	outb(reg, REG);	return inb(VAL);}/* logical device for fans is 0x0A */#define superio_select() superio_outb(0x07, 0x0A)static inline voidsuperio_enter(void){	outb(0x55, REG);}static inline voidsuperio_exit(void){	outb(0xAA, REG);}#define SUPERIO_REG_ACT		0x30#define SUPERIO_REG_BASE	0x60#define SUPERIO_REG_DEVID	0x20/* Logical device registers */#define SMSC_EXTENT		0x80/* nr is 0 or 1 in the macros below */#define SMSC47M1_REG_ALARM		0x04#define SMSC47M1_REG_TPIN(nr)		(0x34 - (nr))#define SMSC47M1_REG_PPIN(nr)		(0x36 - (nr))#define SMSC47M1_REG_FANDIV		0x58static const u8 SMSC47M1_REG_FAN[3]		= { 0x59, 0x5a, 0x6b };static const u8 SMSC47M1_REG_FAN_PRELOAD[3]	= { 0x5b, 0x5c, 0x6c };static const u8 SMSC47M1_REG_PWM[3]		= { 0x56, 0x57, 0x69 };#define SMSC47M2_REG_ALARM6		0x09#define SMSC47M2_REG_TPIN1		0x38#define SMSC47M2_REG_TPIN2		0x37#define SMSC47M2_REG_TPIN3		0x2d#define SMSC47M2_REG_PPIN3		0x2c#define SMSC47M2_REG_FANDIV3		0x6a#define MIN_FROM_REG(reg,div)		((reg)>=192 ? 0 : \					 983040/((192-(reg))*(div)))#define FAN_FROM_REG(reg,div,preload)	((reg)<=(preload) || (reg)==255 ? 0 : \					 983040/(((reg)-(preload))*(div)))#define DIV_FROM_REG(reg)		(1 << (reg))#define PWM_FROM_REG(reg)		(((reg) & 0x7E) << 1)#define PWM_EN_FROM_REG(reg)		((~(reg)) & 0x01)#define PWM_TO_REG(reg)			(((reg) >> 1) & 0x7E)struct smsc47m1_data {	unsigned short addr;	const char *name;	enum chips type;	struct device *hwmon_dev;	struct mutex update_lock;	unsigned long last_updated;	/* In jiffies */	u8 fan[3];		/* Register value */	u8 fan_preload[3];	/* Register value */	u8 fan_div[3];		/* Register encoding, shifted right */	u8 alarms;		/* Register encoding */	u8 pwm[3];		/* Register value (bit 0 is disable) */};struct smsc47m1_sio_data {	enum chips type;};static int smsc47m1_probe(struct platform_device *pdev);static int __devexit smsc47m1_remove(struct platform_device *pdev);static struct smsc47m1_data *smsc47m1_update_device(struct device *dev,		int init);static inline int smsc47m1_read_value(struct smsc47m1_data *data, u8 reg){	return inb_p(data->addr + reg);}static inline void smsc47m1_write_value(struct smsc47m1_data *data, u8 reg,		u8 value){	outb_p(value, data->addr + reg);}static struct platform_driver smsc47m1_driver = {	.driver = {		.owner	= THIS_MODULE,		.name	= DRVNAME,	},	.probe		= smsc47m1_probe,	.remove		= __devexit_p(smsc47m1_remove),};static ssize_t get_fan(struct device *dev, struct device_attribute		       *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	int nr = attr->index;	/* This chip (stupidly) stops monitoring fan speed if PWM is	   enabled and duty cycle is 0%. This is fine if the monitoring	   and control concern the same fan, but troublesome if they are	   not (which could as well happen). */	int rpm = (data->pwm[nr] & 0x7F) == 0x00 ? 0 :		  FAN_FROM_REG(data->fan[nr],			       DIV_FROM_REG(data->fan_div[nr]),			       data->fan_preload[nr]);	return sprintf(buf, "%d\n", rpm);}static ssize_t get_fan_min(struct device *dev, struct device_attribute			   *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	int nr = attr->index;	int rpm = MIN_FROM_REG(data->fan_preload[nr],			       DIV_FROM_REG(data->fan_div[nr]));	return sprintf(buf, "%d\n", rpm);}static ssize_t get_fan_div(struct device *dev, struct device_attribute			   *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[attr->index]));}static ssize_t get_pwm(struct device *dev, struct device_attribute		       *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[attr->index]));}static ssize_t get_pwm_en(struct device *dev, struct device_attribute			  *devattr, char *buf){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[attr->index]));}static ssize_t get_alarms(struct device *dev, struct device_attribute			  *devattr, char *buf){	struct smsc47m1_data *data = smsc47m1_update_device(dev, 0);	return sprintf(buf, "%d\n", data->alarms);}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 smsc47m1_data *data = dev_get_drvdata(dev);	int nr = attr->index;	long rpmdiv, val = simple_strtol(buf, NULL, 10);	mutex_lock(&data->update_lock);	rpmdiv = val * DIV_FROM_REG(data->fan_div[nr]);	if (983040 > 192 * rpmdiv || 2 * rpmdiv > 983040) {		mutex_unlock(&data->update_lock);		return -EINVAL;	}	data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv);	smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],			     data->fan_preload[nr]);	mutex_unlock(&data->update_lock);	return count;}/* Note: we save and restore the fan minimum here, because its value is   determined in part by the fan clock divider.  This follows the principle   of least surprise; the user doesn't expect the fan minimum to change just   because the divider changed. */static ssize_t set_fan_div(struct device *dev, struct device_attribute			   *devattr, const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = dev_get_drvdata(dev);	int nr = attr->index;	long new_div = simple_strtol(buf, NULL, 10), tmp;	u8 old_div = DIV_FROM_REG(data->fan_div[nr]);	if (new_div == old_div) /* No change */		return count;	mutex_lock(&data->update_lock);	switch (new_div) {	case 1: data->fan_div[nr] = 0; break;	case 2: data->fan_div[nr] = 1; break;	case 4: data->fan_div[nr] = 2; break;	case 8: data->fan_div[nr] = 3; break;	default:		mutex_unlock(&data->update_lock);		return -EINVAL;	}	switch (nr) {	case 0:	case 1:		tmp = smsc47m1_read_value(data, SMSC47M1_REG_FANDIV)		      & ~(0x03 << (4 + 2 * nr));		tmp |= data->fan_div[nr] << (4 + 2 * nr);		smsc47m1_write_value(data, SMSC47M1_REG_FANDIV, tmp);		break;	case 2:		tmp = smsc47m1_read_value(data, SMSC47M2_REG_FANDIV3) & 0xCF;		tmp |= data->fan_div[2] << 4;		smsc47m1_write_value(data, SMSC47M2_REG_FANDIV3, tmp);		break;	}	/* Preserve fan min */	tmp = 192 - (old_div * (192 - data->fan_preload[nr])		     + new_div / 2) / new_div;	data->fan_preload[nr] = SENSORS_LIMIT(tmp, 0, 191);	smsc47m1_write_value(data, SMSC47M1_REG_FAN_PRELOAD[nr],			     data->fan_preload[nr]);	mutex_unlock(&data->update_lock);	return count;}static ssize_t set_pwm(struct device *dev, struct device_attribute		       *devattr, const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = dev_get_drvdata(dev);	int nr = attr->index;	long val = simple_strtol(buf, NULL, 10);	if (val < 0 || val > 255)		return -EINVAL;	mutex_lock(&data->update_lock);	data->pwm[nr] &= 0x81; /* Preserve additional bits */	data->pwm[nr] |= PWM_TO_REG(val);	smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],			     data->pwm[nr]);	mutex_unlock(&data->update_lock);	return count;}static ssize_t set_pwm_en(struct device *dev, struct device_attribute			  *devattr, const char *buf, size_t count){	struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);	struct smsc47m1_data *data = dev_get_drvdata(dev);	int nr = attr->index;	long val = simple_strtol(buf, NULL, 10);		if (val != 0 && val != 1)		return -EINVAL;	mutex_lock(&data->update_lock);	data->pwm[nr] &= 0xFE; /* preserve the other bits */	data->pwm[nr] |= !val;	smsc47m1_write_value(data, SMSC47M1_REG_PWM[nr],			     data->pwm[nr]);	mutex_unlock(&data->update_lock);	return count;}#define fan_present(offset)						\static SENSOR_DEVICE_ATTR(fan##offset##_input, S_IRUGO, get_fan,	\		NULL, offset - 1);					\static SENSOR_DEVICE_ATTR(fan##offset##_min, S_IRUGO | S_IWUSR,		\		get_fan_min, set_fan_min, offset - 1);			\static SENSOR_DEVICE_ATTR(fan##offset##_div, S_IRUGO | S_IWUSR,		\		get_fan_div, set_fan_div, offset - 1);			\static SENSOR_DEVICE_ATTR(pwm##offset, S_IRUGO | S_IWUSR,		\		get_pwm, set_pwm, offset - 1);				\static SENSOR_DEVICE_ATTR(pwm##offset##_enable, S_IRUGO | S_IWUSR,	\		get_pwm_en, set_pwm_en, offset - 1)fan_present(1);fan_present(2);fan_present(3);static DEVICE_ATTR(alarms, S_IRUGO, get_alarms, NULL);static ssize_t show_name(struct device *dev, struct device_attribute			 *devattr, char *buf){	struct smsc47m1_data *data = dev_get_drvdata(dev);	return sprintf(buf, "%s\n", data->name);}static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);/* Almost all sysfs files may or may not be created depending on the chip   setup so we create them individually. It is still convenient to define a   group to remove them all at once. */static struct attribute *smsc47m1_attributes[] = {	&sensor_dev_attr_fan1_input.dev_attr.attr,	&sensor_dev_attr_fan1_min.dev_attr.attr,	&sensor_dev_attr_fan1_div.dev_attr.attr,	&sensor_dev_attr_fan2_input.dev_attr.attr,	&sensor_dev_attr_fan2_min.dev_attr.attr,	&sensor_dev_attr_fan2_div.dev_attr.attr,	&sensor_dev_attr_fan3_input.dev_attr.attr,	&sensor_dev_attr_fan3_min.dev_attr.attr,	&sensor_dev_attr_fan3_div.dev_attr.attr,	&sensor_dev_attr_pwm1.dev_attr.attr,

⌨️ 快捷键说明

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