📄 smsc47m1.c
字号:
/* smsc47m1.c - Part of lm_sensors, Linux kernel modules for hardware monitoring Supports the SMSC LPC47B27x, LPC47M10x, LPC47M13x and LPC47M14x Super-I/O chips. Copyright (C) 2002 Mark D. Studebaker <mdsxyz123@yahoo.com> Copyright (C) 2004 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/i2c.h>#include <linux/i2c-sensor.h>#include <linux/init.h>#include <asm/io.h>static unsigned short normal_i2c[] = { I2C_CLIENT_END };/* Address is autodetected, there is no default value */static unsigned int normal_isa[] = { 0x0000, I2C_CLIENT_ISA_END };static struct i2c_force_data forces[] = {{NULL}};enum chips { any_chip, smsc47m1 };static struct i2c_address_data addr_data = { .normal_i2c = normal_i2c, .normal_isa = normal_isa, .forces = forces,};/* 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_PWM(nr) (0x56 + (nr))#define SMSC47M1_REG_FANDIV 0x58#define SMSC47M1_REG_FAN(nr) (0x59 + (nr))#define SMSC47M1_REG_FAN_PRELOAD(nr) (0x5B + (nr))#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 { struct i2c_client client; struct semaphore lock; int sysctl_id; struct semaphore update_lock; unsigned long last_updated; /* In jiffies */ u8 fan[2]; /* Register value */ u8 fan_preload[2]; /* Register value */ u8 fan_div[2]; /* Register encoding, shifted right */ u8 alarms; /* Register encoding */ u8 pwm[2]; /* Register value (bit 7 is enable) */};static int smsc47m1_attach_adapter(struct i2c_adapter *adapter);static int smsc47m1_find(int *address);static int smsc47m1_detect(struct i2c_adapter *adapter, int address, int kind);static int smsc47m1_detach_client(struct i2c_client *client);static int smsc47m1_read_value(struct i2c_client *client, u8 reg);static void smsc47m1_write_value(struct i2c_client *client, u8 reg, u8 value);static struct smsc47m1_data *smsc47m1_update_device(struct device *dev, int init);static int smsc47m1_id;static struct i2c_driver smsc47m1_driver = { .owner = THIS_MODULE, .name = "smsc47m1", .id = I2C_DRIVERID_SMSC47M1, .flags = I2C_DF_NOTIFY, .attach_adapter = smsc47m1_attach_adapter, .detach_client = smsc47m1_detach_client,};/* nr is 0 or 1 in the callback functions below */static ssize_t get_fan(struct device *dev, char *buf, int nr){ struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); /* 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, char *buf, int nr){ struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); 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, char *buf, int nr){ struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); return sprintf(buf, "%d\n", DIV_FROM_REG(data->fan_div[nr]));}static ssize_t get_pwm(struct device *dev, char *buf, int nr){ struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); return sprintf(buf, "%d\n", PWM_FROM_REG(data->pwm[nr]));}static ssize_t get_pwm_en(struct device *dev, char *buf, int nr){ struct smsc47m1_data *data = smsc47m1_update_device(dev, 0); return sprintf(buf, "%d\n", PWM_EN_FROM_REG(data->pwm[nr]));}static ssize_t get_alarms(struct device *dev, 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, const char *buf, size_t count, int nr){ struct i2c_client *client = to_i2c_client(dev); struct smsc47m1_data *data = i2c_get_clientdata(client); long rpmdiv = simple_strtol(buf, NULL, 10) * DIV_FROM_REG(data->fan_div[nr]); if (983040 > 192 * rpmdiv || 2 * rpmdiv > 983040) return -EINVAL; data->fan_preload[nr] = 192 - ((983040 + rpmdiv / 2) / rpmdiv); smsc47m1_write_value(client, SMSC47M1_REG_FAN_PRELOAD(nr), data->fan_preload[nr]); 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 suprise; the user doesn't expect the fan minimum to change just because the divider changed. */static ssize_t set_fan_div(struct device *dev, const char *buf, size_t count, int nr){ struct i2c_client *client = to_i2c_client(dev); struct smsc47m1_data *data = i2c_get_clientdata(client); 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; 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: return -EINVAL; } tmp = smsc47m1_read_value(client, SMSC47M1_REG_FANDIV) & 0x0F; tmp |= (data->fan_div[0] << 4) | (data->fan_div[1] << 6); smsc47m1_write_value(client, SMSC47M1_REG_FANDIV, tmp); /* 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(client, SMSC47M1_REG_FAN_PRELOAD(nr), data->fan_preload[nr]); return count;}static ssize_t set_pwm(struct device *dev, const char *buf, size_t count, int nr){ struct i2c_client *client = to_i2c_client(dev); struct smsc47m1_data *data = i2c_get_clientdata(client); long val = simple_strtol(buf, NULL, 10); if (val < 0 || val > 255) return -EINVAL; data->pwm[nr] &= 0x81; /* Preserve additional bits */ data->pwm[nr] |= PWM_TO_REG(val); smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr), data->pwm[nr]); return count;}static ssize_t set_pwm_en(struct device *dev, const char *buf, size_t count, int nr){ struct i2c_client *client = to_i2c_client(dev); struct smsc47m1_data *data = i2c_get_clientdata(client); long val = simple_strtol(buf, NULL, 10); if (val != 0 && val != 1) return -EINVAL; data->pwm[nr] &= 0xFE; /* preserve the other bits */ data->pwm[nr] |= !val; smsc47m1_write_value(client, SMSC47M1_REG_PWM(nr), data->pwm[nr]); return count;}#define fan_present(offset) \static ssize_t get_fan##offset (struct device *dev, char *buf) \{ \ return get_fan(dev, buf, offset - 1); \
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -