📄 pc87360.c
字号:
/* * pc87360.c - Part of lm_sensors, Linux kernel modules * for hardware monitoring * Copyright (C) 2004 Jean Delvare <khali@linux-fr.org> * * Copied from smsc47m1.c: * Copyright (C) 2002 Mark D. 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. * * Supports the following chips: * * Chip #vin #fan #pwm #temp devid * PC87360 - 2 2 - 0xE1 * PC87363 - 2 2 - 0xE8 * PC87364 - 3 3 - 0xE4 * PC87365 11 3 3 2 0xE5 * PC87366 11 3 3 3-4 0xE9 * * This driver assumes that no more than one chip is present, and one of * the standard Super-I/O addresses is used (0x2E/0x2F or 0x4E/0x4F). */#include <linux/config.h>#include <linux/module.h>#include <linux/init.h>#include <linux/slab.h>#include <linux/i2c.h>#include <linux/i2c-sensor.h>#include <linux/i2c-vid.h>#include <asm/io.h>static unsigned short normal_i2c[] = { I2C_CLIENT_END };static unsigned int normal_isa[] = { 0, I2C_CLIENT_ISA_END };static struct i2c_force_data forces[] = {{ NULL }};static u8 devid;static unsigned int extra_isa[3];static u8 confreg[4];enum chips { any_chip, pc87360, pc87363, pc87364, pc87365, pc87366 };static struct i2c_address_data addr_data = { .normal_i2c = normal_i2c, .normal_isa = normal_isa, .forces = forces,};static int init = 1;module_param(init, int, 0);MODULE_PARM_DESC(init, "Chip initialization level:\n" " 0: None\n" "*1: Forcibly enable internal voltage and temperature channels, except in9\n" " 2: Forcibly enable all voltage and temperature channels, except in9\n" " 3: Forcibly enable all voltage and temperature channels, including in9");/* * Super-I/O registers and operations */#define DEV 0x07 /* Register: Logical device select */#define DEVID 0x20 /* Register: Device ID */#define ACT 0x30 /* Register: Device activation */#define BASE 0x60 /* Register: Base address */#define FSCM 0x09 /* Logical device: fans */#define VLM 0x0d /* Logical device: voltages */#define TMS 0x0e /* Logical device: temperatures */static const u8 logdev[3] = { FSCM, VLM, TMS };#define LD_FAN 0#define LD_IN 1#define LD_TEMP 2static inline void superio_outb(int sioaddr, int reg, int val){ outb(reg, sioaddr); outb(val, sioaddr+1);}static inline int superio_inb(int sioaddr, int reg){ outb(reg, sioaddr); return inb(sioaddr+1);}static inline void superio_exit(int sioaddr){ outb(0x02, sioaddr); outb(0x02, sioaddr+1);}/* * Logical devices */#define PC87360_EXTENT 0x10#define PC87365_REG_BANK 0x09#define NO_BANK 0xff/* * Fan registers and conversions *//* nr has to be 0 or 1 (PC87360/87363) or 2 (PC87364/87365/87366) */#define PC87360_REG_PRESCALE(nr) (0x00 + 2 * (nr))#define PC87360_REG_PWM(nr) (0x01 + 2 * (nr))#define PC87360_REG_FAN_MIN(nr) (0x06 + 3 * (nr))#define PC87360_REG_FAN(nr) (0x07 + 3 * (nr))#define PC87360_REG_FAN_STATUS(nr) (0x08 + 3 * (nr))#define FAN_FROM_REG(val,div) ((val) == 0 ? 0: \ 480000 / ((val)*(div)))#define FAN_TO_REG(val,div) ((val) <= 100 ? 0 : \ 480000 / ((val)*(div)))#define FAN_DIV_FROM_REG(val) (1 << ((val >> 5) & 0x03))#define FAN_STATUS_FROM_REG(val) ((val) & 0x07)#define FAN_CONFIG_MONITOR(val,nr) (((val) >> (2 + nr * 3)) & 1)#define FAN_CONFIG_CONTROL(val,nr) (((val) >> (3 + nr * 3)) & 1)#define FAN_CONFIG_INVERT(val,nr) (((val) >> (4 + nr * 3)) & 1)#define PWM_FROM_REG(val,inv) ((inv) ? 255 - (val) : (val))static inline u8 PWM_TO_REG(int val, int inv){ if (inv) val = 255 - val; if (val < 0) return 0; if (val > 255) return 255; return val;}/* * Voltage registers and conversions */#define PC87365_REG_IN_CONVRATE 0x07#define PC87365_REG_IN_CONFIG 0x08#define PC87365_REG_IN 0x0B#define PC87365_REG_IN_MIN 0x0D#define PC87365_REG_IN_MAX 0x0C#define PC87365_REG_IN_STATUS 0x0A#define PC87365_REG_IN_ALARMS1 0x00#define PC87365_REG_IN_ALARMS2 0x01#define PC87365_REG_VID 0x06#define IN_FROM_REG(val,ref) (((val) * (ref) + 128) / 256)#define IN_TO_REG(val,ref) ((val) < 0 ? 0 : \ (val)*256 >= (ref)*255 ? 255: \ ((val) * 256 + (ref)/2) / (ref))/* * Temperature registers and conversions */#define PC87365_REG_TEMP_CONFIG 0x08#define PC87365_REG_TEMP 0x0B#define PC87365_REG_TEMP_MIN 0x0D#define PC87365_REG_TEMP_MAX 0x0C#define PC87365_REG_TEMP_CRIT 0x0E#define PC87365_REG_TEMP_STATUS 0x0A#define PC87365_REG_TEMP_ALARMS 0x00#define TEMP_FROM_REG(val) ((val) * 1000)#define TEMP_TO_REG(val) ((val) < -55000 ? -55 : \ (val) > 127000 ? 127 : \ (val) < 0 ? ((val) - 500) / 1000 : \ ((val) + 500) / 1000)/* * Client data (each client gets its own) */struct pc87360_data { struct i2c_client client; struct semaphore lock; struct semaphore update_lock; char valid; /* !=0 if following fields are valid */ unsigned long last_updated; /* In jiffies */ int address[3]; u8 fannr, innr, tempnr; u8 fan[3]; /* Register value */ u8 fan_min[3]; /* Register value */ u8 fan_status[3]; /* Register value */ u8 pwm[3]; /* Register value */ u16 fan_conf; /* Configuration register values, combined */ u16 in_vref; /* 1 mV/bit */ u8 in[14]; /* Register value */ u8 in_min[14]; /* Register value */ u8 in_max[14]; /* Register value */ u8 in_crit[3]; /* Register value */ u8 in_status[14]; /* Register value */ u16 in_alarms; /* Register values, combined, masked */ u8 vid_conf; /* Configuration register value */ u8 vrm; u8 vid; /* Register value */ s8 temp[3]; /* Register value */ s8 temp_min[3]; /* Register value */ s8 temp_max[3]; /* Register value */ s8 temp_crit[3]; /* Register value */ u8 temp_status[3]; /* Register value */ u8 temp_alarms; /* Register value, masked */};/* * Functions declaration */static int pc87360_attach_adapter(struct i2c_adapter *adapter);static int pc87360_detect(struct i2c_adapter *adapter, int address, int kind);static int pc87360_detach_client(struct i2c_client *client);static int pc87360_read_value(struct pc87360_data *data, u8 ldi, u8 bank, u8 reg);static void pc87360_write_value(struct pc87360_data *data, u8 ldi, u8 bank, u8 reg, u8 value);static void pc87360_init_client(struct i2c_client *client, int use_thermistors);static struct pc87360_data *pc87360_update_device(struct device *dev);/* * Driver data (common to all clients) */static struct i2c_driver pc87360_driver = { .owner = THIS_MODULE, .name = "pc87360", .flags = I2C_DF_NOTIFY, .attach_adapter = pc87360_attach_adapter, .detach_client = pc87360_detach_client,};/* * Sysfs stuff */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 pc87360_data *data = i2c_get_clientdata(client); long fan_min = simple_strtol(buf, NULL, 10); fan_min = FAN_TO_REG(fan_min, FAN_DIV_FROM_REG(data->fan_status[nr])); /* If it wouldn't fit, change clock divisor */ while (fan_min > 255 && (data->fan_status[nr] & 0x60) != 0x60) { fan_min >>= 1; data->fan[nr] >>= 1; data->fan_status[nr] += 0x20; } data->fan_min[nr] = fan_min > 255 ? 255 : fan_min; pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_FAN_MIN(nr), data->fan_min[nr]); /* Write new divider, preserve alarm bits */ pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_FAN_STATUS(nr), data->fan_status[nr] & 0xF9); return count;}#define show_and_set_fan(offset) \static ssize_t show_fan##offset##_input(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan[offset-1], \ FAN_DIV_FROM_REG(data->fan_status[offset-1]))); \} \static ssize_t show_fan##offset##_min(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", FAN_FROM_REG(data->fan_min[offset-1], \ FAN_DIV_FROM_REG(data->fan_status[offset-1]))); \} \static ssize_t show_fan##offset##_div(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", \ FAN_DIV_FROM_REG(data->fan_status[offset-1])); \} \static ssize_t show_fan##offset##_status(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", \ FAN_STATUS_FROM_REG(data->fan_status[offset-1])); \} \static ssize_t set_fan##offset##_min(struct device *dev, const char *buf, \ size_t count) \{ \ return set_fan_min(dev, buf, count, offset-1); \} \static DEVICE_ATTR(fan##offset##_input, S_IRUGO, \ show_fan##offset##_input, NULL); \static DEVICE_ATTR(fan##offset##_min, S_IWUSR | S_IRUGO, \ show_fan##offset##_min, set_fan##offset##_min); \static DEVICE_ATTR(fan##offset##_div, S_IRUGO, \ show_fan##offset##_div, NULL); \static DEVICE_ATTR(fan##offset##_status, S_IRUGO, \ show_fan##offset##_status, NULL);show_and_set_fan(1)show_and_set_fan(2)show_and_set_fan(3)#define show_and_set_pwm(offset) \static ssize_t show_pwm##offset(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", \ PWM_FROM_REG(data->pwm[offset-1], \ FAN_CONFIG_INVERT(data->fan_conf, \ offset-1))); \} \static ssize_t set_pwm##offset(struct device *dev, const char *buf, \ size_t count) \{ \ struct i2c_client *client = to_i2c_client(dev); \ struct pc87360_data *data = i2c_get_clientdata(client); \ long val = simple_strtol(buf, NULL, 10); \ data->pwm[offset-1] = PWM_TO_REG(val, \ FAN_CONFIG_INVERT(data->fan_conf, offset-1)); \ pc87360_write_value(data, LD_FAN, NO_BANK, PC87360_REG_PWM(offset-1), \ data->pwm[offset-1]); \ return count; \} \static DEVICE_ATTR(pwm##offset, S_IWUSR | S_IRUGO, \ show_pwm##offset, set_pwm##offset);show_and_set_pwm(1)show_and_set_pwm(2)show_and_set_pwm(3)#define show_and_set_in(offset) \static ssize_t show_in##offset##_input(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in[offset], \ data->in_vref)); \} \static ssize_t show_in##offset##_min(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[offset], \ data->in_vref)); \} \static ssize_t show_in##offset##_max(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[offset], \ data->in_vref)); \} \static ssize_t show_in##offset##_status(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", data->in_status[offset]); \} \static ssize_t set_in##offset##_min(struct device *dev, const char *buf, \ size_t count) \{ \ struct i2c_client *client = to_i2c_client(dev); \ struct pc87360_data *data = i2c_get_clientdata(client); \ long val = simple_strtol(buf, NULL, 10); \ data->in_min[offset] = IN_TO_REG(val, data->in_vref); \ pc87360_write_value(data, LD_IN, offset, PC87365_REG_IN_MIN, \ data->in_min[offset]); \ return count; \} \static ssize_t set_in##offset##_max(struct device *dev, const char *buf, \ size_t count) \{ \ struct i2c_client *client = to_i2c_client(dev); \ struct pc87360_data *data = i2c_get_clientdata(client); \ long val = simple_strtol(buf, NULL, 10); \ data->in_max[offset] = IN_TO_REG(val, \ data->in_vref); \ pc87360_write_value(data, LD_IN, offset, PC87365_REG_IN_MAX, \ data->in_max[offset]); \ return count; \} \static DEVICE_ATTR(in##offset##_input, S_IRUGO, \ show_in##offset##_input, NULL); \static DEVICE_ATTR(in##offset##_min, S_IWUSR | S_IRUGO, \ show_in##offset##_min, set_in##offset##_min); \static DEVICE_ATTR(in##offset##_max, S_IWUSR | S_IRUGO, \ show_in##offset##_max, set_in##offset##_max); \static DEVICE_ATTR(in##offset##_status, S_IRUGO, \ show_in##offset##_status, NULL);show_and_set_in(0)show_and_set_in(1)show_and_set_in(2)show_and_set_in(3)show_and_set_in(4)show_and_set_in(5)show_and_set_in(6)show_and_set_in(7)show_and_set_in(8)show_and_set_in(9)show_and_set_in(10)#define show_and_set_therm(offset) \static ssize_t show_temp##offset##_input(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in[offset+7], \ data->in_vref)); \} \static ssize_t show_temp##offset##_min(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in_min[offset+7], \ data->in_vref)); \} \static ssize_t show_temp##offset##_max(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in_max[offset+7], \ data->in_vref)); \} \static ssize_t show_temp##offset##_crit(struct device *dev, char *buf) \{ \ struct pc87360_data *data = pc87360_update_device(dev); \ return sprintf(buf, "%u\n", IN_FROM_REG(data->in_crit[offset-4], \ data->in_vref)); \} \
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -