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 + -
显示快捷键?