| /* |
| * nct6775 - Driver for the hardware monitoring functionality of |
| * Nuvoton NCT677x Super-I/O chips |
| * |
| * Copyright (C) 2012 Guenter Roeck <linux@roeck-us.net> |
| * |
| * Derived from w83627ehf driver |
| * Copyright (C) 2005-2012 Jean Delvare <khali@linux-fr.org> |
| * Copyright (C) 2006 Yuan Mu (Winbond), |
| * Rudolf Marek <r.marek@assembler.cz> |
| * David Hubbard <david.c.hubbard@gmail.com> |
| * Daniel J Blueman <daniel.blueman@gmail.com> |
| * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) |
| * |
| * Shamelessly ripped from the w83627hf driver |
| * Copyright (C) 2003 Mark Studebaker |
| * |
| * 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 chip IDs man ID |
| * nct6775f 9 4 3 6+3 0xb470 0xc1 0x5ca3 |
| * nct6776f 9 5 3 6+3 0xc330 0xc1 0x5ca3 |
| * nct6779d 15 5 5 2+6 0xc560 0xc1 0x5ca3 |
| * |
| * #temp lists the number of monitored temperature sources (first value) plus |
| * the number of directly connectable temperature sensors (second value). |
| */ |
| |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/slab.h> |
| #include <linux/jiffies.h> |
| #include <linux/platform_device.h> |
| #include <linux/hwmon.h> |
| #include <linux/hwmon-sysfs.h> |
| #include <linux/hwmon-vid.h> |
| #include <linux/err.h> |
| #include <linux/mutex.h> |
| #include <linux/acpi.h> |
| #include <linux/io.h> |
| #include "lm75.h" |
| |
| enum kinds { nct6775, nct6776, nct6779 }; |
| |
| /* used to set data->name = nct6775_device_names[data->sio_kind] */ |
| static const char * const nct6775_device_names[] = { |
| "nct6775", |
| "nct6776", |
| "nct6779", |
| }; |
| |
| static unsigned short force_id; |
| module_param(force_id, ushort, 0); |
| MODULE_PARM_DESC(force_id, "Override the detected device ID"); |
| |
| #define DRVNAME "nct6775" |
| |
| /* |
| * Super-I/O constants and functions |
| */ |
| |
| #define NCT6775_LD_HWM 0x0b |
| #define NCT6775_LD_VID 0x0d |
| |
| #define SIO_REG_LDSEL 0x07 /* Logical device select */ |
| #define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ |
| #define SIO_REG_ENABLE 0x30 /* Logical device enable */ |
| #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ |
| |
| #define SIO_NCT6775_ID 0xb470 |
| #define SIO_NCT6776_ID 0xc330 |
| #define SIO_NCT6779_ID 0xc560 |
| #define SIO_ID_MASK 0xFFF0 |
| |
| static inline void |
| superio_outb(int ioreg, int reg, int val) |
| { |
| outb(reg, ioreg); |
| outb(val, ioreg + 1); |
| } |
| |
| static inline int |
| superio_inb(int ioreg, int reg) |
| { |
| outb(reg, ioreg); |
| return inb(ioreg + 1); |
| } |
| |
| static inline void |
| superio_select(int ioreg, int ld) |
| { |
| outb(SIO_REG_LDSEL, ioreg); |
| outb(ld, ioreg + 1); |
| } |
| |
| static inline int |
| superio_enter(int ioreg) |
| { |
| /* |
| * Try to reserve <ioreg> and <ioreg + 1> for exclusive access. |
| */ |
| if (!request_muxed_region(ioreg, 2, DRVNAME)) |
| return -EBUSY; |
| |
| outb(0x87, ioreg); |
| outb(0x87, ioreg); |
| |
| return 0; |
| } |
| |
| static inline void |
| superio_exit(int ioreg) |
| { |
| outb(0xaa, ioreg); |
| outb(0x02, ioreg); |
| outb(0x02, ioreg + 1); |
| release_region(ioreg, 2); |
| } |
| |
| /* |
| * ISA constants |
| */ |
| |
| #define IOREGION_ALIGNMENT (~7) |
| #define IOREGION_OFFSET 5 |
| #define IOREGION_LENGTH 2 |
| #define ADDR_REG_OFFSET 0 |
| #define DATA_REG_OFFSET 1 |
| |
| #define NCT6775_REG_BANK 0x4E |
| #define NCT6775_REG_CONFIG 0x40 |
| |
| /* |
| * Not currently used: |
| * REG_MAN_ID has the value 0x5ca3 for all supported chips. |
| * REG_CHIP_ID == 0x88/0xa1/0xc1 depending on chip model. |
| * REG_MAN_ID is at port 0x4f |
| * REG_CHIP_ID is at port 0x58 |
| */ |
| |
| #define NUM_REG_ALARM 4 /* Max number of alarm registers */ |
| |
| /* Common and NCT6775 specific data */ |
| |
| /* Voltage min/max registers for nr=7..14 are in bank 5 */ |
| |
| static const u16 NCT6775_REG_IN_MAX[] = { |
| 0x2b, 0x2d, 0x2f, 0x31, 0x33, 0x35, 0x37, 0x554, 0x556, 0x558, 0x55a, |
| 0x55c, 0x55e, 0x560, 0x562 }; |
| static const u16 NCT6775_REG_IN_MIN[] = { |
| 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x555, 0x557, 0x559, 0x55b, |
| 0x55d, 0x55f, 0x561, 0x563 }; |
| static const u16 NCT6775_REG_IN[] = { |
| 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x550, 0x551, 0x552 |
| }; |
| |
| #define NCT6775_REG_VBAT 0x5D |
| |
| static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; |
| |
| /* 0..15 voltages, 16..23 fans, 24..31 temperatures */ |
| |
| static const s8 NCT6775_ALARM_BITS[] = { |
| 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ |
| 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ |
| -1, /* unused */ |
| 6, 7, 11, 10, 23, /* fan1..fan5 */ |
| -1, -1, -1, /* unused */ |
| 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ |
| 12, -1 }; /* intrusion0, intrusion1 */ |
| |
| /* NCT6776 specific data */ |
| |
| static const s8 NCT6776_ALARM_BITS[] = { |
| 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ |
| 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ |
| -1, /* unused */ |
| 6, 7, 11, 10, 23, /* fan1..fan5 */ |
| -1, -1, -1, /* unused */ |
| 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ |
| 12, 9 }; /* intrusion0, intrusion1 */ |
| |
| /* NCT6779 specific data */ |
| |
| static const u16 NCT6779_REG_IN[] = { |
| 0x480, 0x481, 0x482, 0x483, 0x484, 0x485, 0x486, 0x487, |
| 0x488, 0x489, 0x48a, 0x48b, 0x48c, 0x48d, 0x48e }; |
| |
| static const u16 NCT6779_REG_ALARM[NUM_REG_ALARM] = { |
| 0x459, 0x45A, 0x45B, 0x568 }; |
| |
| static const s8 NCT6779_ALARM_BITS[] = { |
| 0, 1, 2, 3, 8, 21, 20, 16, /* in0.. in7 */ |
| 17, 24, 25, 26, 27, 28, 29, /* in8..in14 */ |
| -1, /* unused */ |
| 6, 7, 11, 10, 23, /* fan1..fan5 */ |
| -1, -1, -1, /* unused */ |
| 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ |
| 12, 9 }; /* intrusion0, intrusion1 */ |
| |
| /* |
| * Conversions |
| */ |
| |
| /* |
| * Some of the voltage inputs have internal scaling, the tables below |
| * contain 8 (the ADC LSB in mV) * scaling factor * 100 |
| */ |
| static const u16 scale_in[15] = { |
| 800, 800, 1600, 1600, 800, 800, 800, 1600, 1600, 800, 800, 800, 800, |
| 800, 800 |
| }; |
| |
| static inline long in_from_reg(u8 reg, u8 nr) |
| { |
| return DIV_ROUND_CLOSEST(reg * scale_in[nr], 100); |
| } |
| |
| static inline u8 in_to_reg(u32 val, u8 nr) |
| { |
| return clamp_val(DIV_ROUND_CLOSEST(val * 100, scale_in[nr]), 0, 255); |
| } |
| |
| /* |
| * Data structures and manipulation thereof |
| */ |
| |
| struct nct6775_data { |
| int addr; /* IO base of hw monitor block */ |
| enum kinds kind; |
| const char *name; |
| |
| struct device *hwmon_dev; |
| struct mutex lock; |
| |
| u16 REG_CONFIG; |
| u16 REG_VBAT; |
| |
| const s8 *ALARM_BITS; |
| |
| const u16 *REG_VIN; |
| const u16 *REG_IN_MINMAX[2]; |
| |
| const u16 *REG_ALARM; |
| |
| struct mutex update_lock; |
| bool valid; /* true if following fields are valid */ |
| unsigned long last_updated; /* In jiffies */ |
| |
| /* Register values */ |
| u8 bank; /* current register bank */ |
| u8 in_num; /* number of in inputs we have */ |
| u8 in[15][3]; /* [0]=in, [1]=in_max, [2]=in_min */ |
| |
| u64 alarms; |
| |
| u8 vid; |
| u8 vrm; |
| |
| u16 have_in; |
| }; |
| |
| struct nct6775_sio_data { |
| int sioreg; |
| enum kinds kind; |
| }; |
| |
| static bool is_word_sized(struct nct6775_data *data, u16 reg) |
| { |
| switch (data->kind) { |
| case nct6775: |
| return (((reg & 0xff00) == 0x100 || |
| (reg & 0xff00) == 0x200) && |
| ((reg & 0x00ff) == 0x50 || |
| (reg & 0x00ff) == 0x53 || |
| (reg & 0x00ff) == 0x55)) || |
| (reg & 0xfff0) == 0x630 || |
| reg == 0x640 || reg == 0x642 || |
| reg == 0x662 || |
| ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || |
| reg == 0x73 || reg == 0x75 || reg == 0x77; |
| case nct6776: |
| return (((reg & 0xff00) == 0x100 || |
| (reg & 0xff00) == 0x200) && |
| ((reg & 0x00ff) == 0x50 || |
| (reg & 0x00ff) == 0x53 || |
| (reg & 0x00ff) == 0x55)) || |
| (reg & 0xfff0) == 0x630 || |
| reg == 0x402 || |
| reg == 0x640 || reg == 0x642 || |
| ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || |
| reg == 0x73 || reg == 0x75 || reg == 0x77; |
| case nct6779: |
| return reg == 0x150 || reg == 0x153 || reg == 0x155 || |
| ((reg & 0xfff0) == 0x4b0 && (reg & 0x000f) < 0x09) || |
| reg == 0x402 || |
| reg == 0x63a || reg == 0x63c || reg == 0x63e || |
| reg == 0x640 || reg == 0x642 || |
| reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 || |
| reg == 0x7b; |
| } |
| return false; |
| } |
| |
| /* |
| * On older chips, only registers 0x50-0x5f are banked. |
| * On more recent chips, all registers are banked. |
| * Assume that is the case and set the bank number for each access. |
| * Cache the bank number so it only needs to be set if it changes. |
| */ |
| static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) |
| { |
| u8 bank = reg >> 8; |
| if (data->bank != bank) { |
| outb_p(NCT6775_REG_BANK, data->addr + ADDR_REG_OFFSET); |
| outb_p(bank, data->addr + DATA_REG_OFFSET); |
| data->bank = bank; |
| } |
| } |
| |
| static u16 nct6775_read_value(struct nct6775_data *data, u16 reg) |
| { |
| int res, word_sized = is_word_sized(data, reg); |
| |
| mutex_lock(&data->lock); |
| |
| nct6775_set_bank(data, reg); |
| outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); |
| res = inb_p(data->addr + DATA_REG_OFFSET); |
| if (word_sized) { |
| outb_p((reg & 0xff) + 1, |
| data->addr + ADDR_REG_OFFSET); |
| res = (res << 8) + inb_p(data->addr + DATA_REG_OFFSET); |
| } |
| |
| mutex_unlock(&data->lock); |
| return res; |
| } |
| |
| static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) |
| { |
| int word_sized = is_word_sized(data, reg); |
| |
| mutex_lock(&data->lock); |
| |
| nct6775_set_bank(data, reg); |
| outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); |
| if (word_sized) { |
| outb_p(value >> 8, data->addr + DATA_REG_OFFSET); |
| outb_p((reg & 0xff) + 1, |
| data->addr + ADDR_REG_OFFSET); |
| } |
| outb_p(value & 0xff, data->addr + DATA_REG_OFFSET); |
| |
| mutex_unlock(&data->lock); |
| return 0; |
| } |
| |
| static struct nct6775_data *nct6775_update_device(struct device *dev) |
| { |
| struct nct6775_data *data = dev_get_drvdata(dev); |
| int i; |
| |
| mutex_lock(&data->update_lock); |
| |
| if (time_after(jiffies, data->last_updated + HZ + HZ/2) |
| || !data->valid) { |
| /* Measured voltages and limits */ |
| for (i = 0; i < data->in_num; i++) { |
| if (!(data->have_in & (1 << i))) |
| continue; |
| |
| data->in[i][0] = nct6775_read_value(data, |
| data->REG_VIN[i]); |
| data->in[i][1] = nct6775_read_value(data, |
| data->REG_IN_MINMAX[0][i]); |
| data->in[i][2] = nct6775_read_value(data, |
| data->REG_IN_MINMAX[1][i]); |
| } |
| |
| data->alarms = 0; |
| for (i = 0; i < NUM_REG_ALARM; i++) { |
| u8 alarm; |
| if (!data->REG_ALARM[i]) |
| continue; |
| alarm = nct6775_read_value(data, data->REG_ALARM[i]); |
| data->alarms |= ((u64)alarm) << (i << 3); |
| } |
| |
| data->last_updated = jiffies; |
| data->valid = true; |
| } |
| |
| mutex_unlock(&data->update_lock); |
| return data; |
| } |
| |
| /* |
| * Sysfs callback functions |
| */ |
| static ssize_t |
| show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct nct6775_data *data = nct6775_update_device(dev); |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); |
| int nr = sattr->nr; |
| int index = sattr->index; |
| return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr)); |
| } |
| |
| static ssize_t |
| store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, |
| size_t count) |
| { |
| struct nct6775_data *data = dev_get_drvdata(dev); |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); |
| int nr = sattr->nr; |
| int index = sattr->index; |
| unsigned long val; |
| int err = kstrtoul(buf, 10, &val); |
| if (err < 0) |
| return err; |
| mutex_lock(&data->update_lock); |
| data->in[nr][index] = in_to_reg(val, nr); |
| nct6775_write_value(data, data->REG_IN_MINMAX[index-1][nr], |
| data->in[nr][index]); |
| mutex_unlock(&data->update_lock); |
| return count; |
| } |
| |
| static ssize_t |
| show_alarm(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct nct6775_data *data = nct6775_update_device(dev); |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); |
| int nr = data->ALARM_BITS[sattr->index]; |
| return sprintf(buf, "%u\n", |
| (unsigned int)((data->alarms >> nr) & 0x01)); |
| } |
| |
| static SENSOR_DEVICE_ATTR_2(in0_input, S_IRUGO, show_in_reg, NULL, 0, 0); |
| static SENSOR_DEVICE_ATTR_2(in1_input, S_IRUGO, show_in_reg, NULL, 1, 0); |
| static SENSOR_DEVICE_ATTR_2(in2_input, S_IRUGO, show_in_reg, NULL, 2, 0); |
| static SENSOR_DEVICE_ATTR_2(in3_input, S_IRUGO, show_in_reg, NULL, 3, 0); |
| static SENSOR_DEVICE_ATTR_2(in4_input, S_IRUGO, show_in_reg, NULL, 4, 0); |
| static SENSOR_DEVICE_ATTR_2(in5_input, S_IRUGO, show_in_reg, NULL, 5, 0); |
| static SENSOR_DEVICE_ATTR_2(in6_input, S_IRUGO, show_in_reg, NULL, 6, 0); |
| static SENSOR_DEVICE_ATTR_2(in7_input, S_IRUGO, show_in_reg, NULL, 7, 0); |
| static SENSOR_DEVICE_ATTR_2(in8_input, S_IRUGO, show_in_reg, NULL, 8, 0); |
| static SENSOR_DEVICE_ATTR_2(in9_input, S_IRUGO, show_in_reg, NULL, 9, 0); |
| static SENSOR_DEVICE_ATTR_2(in10_input, S_IRUGO, show_in_reg, NULL, 10, 0); |
| static SENSOR_DEVICE_ATTR_2(in11_input, S_IRUGO, show_in_reg, NULL, 11, 0); |
| static SENSOR_DEVICE_ATTR_2(in12_input, S_IRUGO, show_in_reg, NULL, 12, 0); |
| static SENSOR_DEVICE_ATTR_2(in13_input, S_IRUGO, show_in_reg, NULL, 13, 0); |
| static SENSOR_DEVICE_ATTR_2(in14_input, S_IRUGO, show_in_reg, NULL, 14, 0); |
| |
| static SENSOR_DEVICE_ATTR(in0_alarm, S_IRUGO, show_alarm, NULL, 0); |
| static SENSOR_DEVICE_ATTR(in1_alarm, S_IRUGO, show_alarm, NULL, 1); |
| static SENSOR_DEVICE_ATTR(in2_alarm, S_IRUGO, show_alarm, NULL, 2); |
| static SENSOR_DEVICE_ATTR(in3_alarm, S_IRUGO, show_alarm, NULL, 3); |
| static SENSOR_DEVICE_ATTR(in4_alarm, S_IRUGO, show_alarm, NULL, 4); |
| static SENSOR_DEVICE_ATTR(in5_alarm, S_IRUGO, show_alarm, NULL, 5); |
| static SENSOR_DEVICE_ATTR(in6_alarm, S_IRUGO, show_alarm, NULL, 6); |
| static SENSOR_DEVICE_ATTR(in7_alarm, S_IRUGO, show_alarm, NULL, 7); |
| static SENSOR_DEVICE_ATTR(in8_alarm, S_IRUGO, show_alarm, NULL, 8); |
| static SENSOR_DEVICE_ATTR(in9_alarm, S_IRUGO, show_alarm, NULL, 9); |
| static SENSOR_DEVICE_ATTR(in10_alarm, S_IRUGO, show_alarm, NULL, 10); |
| static SENSOR_DEVICE_ATTR(in11_alarm, S_IRUGO, show_alarm, NULL, 11); |
| static SENSOR_DEVICE_ATTR(in12_alarm, S_IRUGO, show_alarm, NULL, 12); |
| static SENSOR_DEVICE_ATTR(in13_alarm, S_IRUGO, show_alarm, NULL, 13); |
| static SENSOR_DEVICE_ATTR(in14_alarm, S_IRUGO, show_alarm, NULL, 14); |
| |
| static SENSOR_DEVICE_ATTR_2(in0_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 0, 1); |
| static SENSOR_DEVICE_ATTR_2(in1_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 1, 1); |
| static SENSOR_DEVICE_ATTR_2(in2_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 2, 1); |
| static SENSOR_DEVICE_ATTR_2(in3_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 3, 1); |
| static SENSOR_DEVICE_ATTR_2(in4_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 4, 1); |
| static SENSOR_DEVICE_ATTR_2(in5_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 5, 1); |
| static SENSOR_DEVICE_ATTR_2(in6_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 6, 1); |
| static SENSOR_DEVICE_ATTR_2(in7_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 7, 1); |
| static SENSOR_DEVICE_ATTR_2(in8_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 8, 1); |
| static SENSOR_DEVICE_ATTR_2(in9_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 9, 1); |
| static SENSOR_DEVICE_ATTR_2(in10_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 10, 1); |
| static SENSOR_DEVICE_ATTR_2(in11_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 11, 1); |
| static SENSOR_DEVICE_ATTR_2(in12_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 12, 1); |
| static SENSOR_DEVICE_ATTR_2(in13_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 13, 1); |
| static SENSOR_DEVICE_ATTR_2(in14_min, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 14, 1); |
| |
| static SENSOR_DEVICE_ATTR_2(in0_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 0, 2); |
| static SENSOR_DEVICE_ATTR_2(in1_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 1, 2); |
| static SENSOR_DEVICE_ATTR_2(in2_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 2, 2); |
| static SENSOR_DEVICE_ATTR_2(in3_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 3, 2); |
| static SENSOR_DEVICE_ATTR_2(in4_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 4, 2); |
| static SENSOR_DEVICE_ATTR_2(in5_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 5, 2); |
| static SENSOR_DEVICE_ATTR_2(in6_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 6, 2); |
| static SENSOR_DEVICE_ATTR_2(in7_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 7, 2); |
| static SENSOR_DEVICE_ATTR_2(in8_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 8, 2); |
| static SENSOR_DEVICE_ATTR_2(in9_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 9, 2); |
| static SENSOR_DEVICE_ATTR_2(in10_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 10, 2); |
| static SENSOR_DEVICE_ATTR_2(in11_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 11, 2); |
| static SENSOR_DEVICE_ATTR_2(in12_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 12, 2); |
| static SENSOR_DEVICE_ATTR_2(in13_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 13, 2); |
| static SENSOR_DEVICE_ATTR_2(in14_max, S_IWUSR | S_IRUGO, show_in_reg, |
| store_in_reg, 14, 2); |
| |
| static struct attribute *nct6775_attributes_in[15][5] = { |
| { |
| &sensor_dev_attr_in0_input.dev_attr.attr, |
| &sensor_dev_attr_in0_min.dev_attr.attr, |
| &sensor_dev_attr_in0_max.dev_attr.attr, |
| &sensor_dev_attr_in0_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in1_input.dev_attr.attr, |
| &sensor_dev_attr_in1_min.dev_attr.attr, |
| &sensor_dev_attr_in1_max.dev_attr.attr, |
| &sensor_dev_attr_in1_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in2_input.dev_attr.attr, |
| &sensor_dev_attr_in2_min.dev_attr.attr, |
| &sensor_dev_attr_in2_max.dev_attr.attr, |
| &sensor_dev_attr_in2_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in3_input.dev_attr.attr, |
| &sensor_dev_attr_in3_min.dev_attr.attr, |
| &sensor_dev_attr_in3_max.dev_attr.attr, |
| &sensor_dev_attr_in3_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in4_input.dev_attr.attr, |
| &sensor_dev_attr_in4_min.dev_attr.attr, |
| &sensor_dev_attr_in4_max.dev_attr.attr, |
| &sensor_dev_attr_in4_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in5_input.dev_attr.attr, |
| &sensor_dev_attr_in5_min.dev_attr.attr, |
| &sensor_dev_attr_in5_max.dev_attr.attr, |
| &sensor_dev_attr_in5_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in6_input.dev_attr.attr, |
| &sensor_dev_attr_in6_min.dev_attr.attr, |
| &sensor_dev_attr_in6_max.dev_attr.attr, |
| &sensor_dev_attr_in6_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in7_input.dev_attr.attr, |
| &sensor_dev_attr_in7_min.dev_attr.attr, |
| &sensor_dev_attr_in7_max.dev_attr.attr, |
| &sensor_dev_attr_in7_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in8_input.dev_attr.attr, |
| &sensor_dev_attr_in8_min.dev_attr.attr, |
| &sensor_dev_attr_in8_max.dev_attr.attr, |
| &sensor_dev_attr_in8_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in9_input.dev_attr.attr, |
| &sensor_dev_attr_in9_min.dev_attr.attr, |
| &sensor_dev_attr_in9_max.dev_attr.attr, |
| &sensor_dev_attr_in9_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in10_input.dev_attr.attr, |
| &sensor_dev_attr_in10_min.dev_attr.attr, |
| &sensor_dev_attr_in10_max.dev_attr.attr, |
| &sensor_dev_attr_in10_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in11_input.dev_attr.attr, |
| &sensor_dev_attr_in11_min.dev_attr.attr, |
| &sensor_dev_attr_in11_max.dev_attr.attr, |
| &sensor_dev_attr_in11_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in12_input.dev_attr.attr, |
| &sensor_dev_attr_in12_min.dev_attr.attr, |
| &sensor_dev_attr_in12_max.dev_attr.attr, |
| &sensor_dev_attr_in12_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in13_input.dev_attr.attr, |
| &sensor_dev_attr_in13_min.dev_attr.attr, |
| &sensor_dev_attr_in13_max.dev_attr.attr, |
| &sensor_dev_attr_in13_alarm.dev_attr.attr, |
| NULL |
| }, |
| { |
| &sensor_dev_attr_in14_input.dev_attr.attr, |
| &sensor_dev_attr_in14_min.dev_attr.attr, |
| &sensor_dev_attr_in14_max.dev_attr.attr, |
| &sensor_dev_attr_in14_alarm.dev_attr.attr, |
| NULL |
| }, |
| }; |
| |
| static const struct attribute_group nct6775_group_in[15] = { |
| { .attrs = nct6775_attributes_in[0] }, |
| { .attrs = nct6775_attributes_in[1] }, |
| { .attrs = nct6775_attributes_in[2] }, |
| { .attrs = nct6775_attributes_in[3] }, |
| { .attrs = nct6775_attributes_in[4] }, |
| { .attrs = nct6775_attributes_in[5] }, |
| { .attrs = nct6775_attributes_in[6] }, |
| { .attrs = nct6775_attributes_in[7] }, |
| { .attrs = nct6775_attributes_in[8] }, |
| { .attrs = nct6775_attributes_in[9] }, |
| { .attrs = nct6775_attributes_in[10] }, |
| { .attrs = nct6775_attributes_in[11] }, |
| { .attrs = nct6775_attributes_in[12] }, |
| { .attrs = nct6775_attributes_in[13] }, |
| { .attrs = nct6775_attributes_in[14] }, |
| }; |
| |
| static ssize_t |
| show_name(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct nct6775_data *data = dev_get_drvdata(dev); |
| |
| return sprintf(buf, "%s\n", data->name); |
| } |
| |
| static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); |
| |
| static ssize_t |
| show_vid(struct device *dev, struct device_attribute *attr, char *buf) |
| { |
| struct nct6775_data *data = dev_get_drvdata(dev); |
| return sprintf(buf, "%d\n", vid_from_reg(data->vid, data->vrm)); |
| } |
| |
| static DEVICE_ATTR(cpu0_vid, S_IRUGO, show_vid, NULL); |
| |
| /* |
| * Driver and device management |
| */ |
| |
| static void nct6775_device_remove_files(struct device *dev) |
| { |
| /* |
| * some entries in the following arrays may not have been used in |
| * device_create_file(), but device_remove_file() will ignore them |
| */ |
| int i; |
| struct nct6775_data *data = dev_get_drvdata(dev); |
| |
| for (i = 0; i < data->in_num; i++) |
| sysfs_remove_group(&dev->kobj, &nct6775_group_in[i]); |
| |
| device_remove_file(dev, &dev_attr_name); |
| device_remove_file(dev, &dev_attr_cpu0_vid); |
| } |
| |
| /* Get the monitoring functions started */ |
| static inline void nct6775_init_device(struct nct6775_data *data) |
| { |
| u8 tmp; |
| |
| /* Start monitoring if needed */ |
| if (data->REG_CONFIG) { |
| tmp = nct6775_read_value(data, data->REG_CONFIG); |
| if (!(tmp & 0x01)) |
| nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); |
| } |
| |
| /* Enable VBAT monitoring if needed */ |
| tmp = nct6775_read_value(data, data->REG_VBAT); |
| if (!(tmp & 0x01)) |
| nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); |
| } |
| |
| static int nct6775_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct nct6775_sio_data *sio_data = dev->platform_data; |
| struct nct6775_data *data; |
| struct resource *res; |
| int i, err = 0; |
| |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); |
| if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, |
| DRVNAME)) |
| return -EBUSY; |
| |
| data = devm_kzalloc(&pdev->dev, sizeof(struct nct6775_data), |
| GFP_KERNEL); |
| if (!data) |
| return -ENOMEM; |
| |
| data->kind = sio_data->kind; |
| data->addr = res->start; |
| mutex_init(&data->lock); |
| mutex_init(&data->update_lock); |
| data->name = nct6775_device_names[data->kind]; |
| data->bank = 0xff; /* Force initial bank selection */ |
| platform_set_drvdata(pdev, data); |
| |
| switch (data->kind) { |
| case nct6775: |
| data->in_num = 9; |
| |
| data->ALARM_BITS = NCT6775_ALARM_BITS; |
| |
| data->REG_CONFIG = NCT6775_REG_CONFIG; |
| data->REG_VBAT = NCT6775_REG_VBAT; |
| data->REG_VIN = NCT6775_REG_IN; |
| data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; |
| data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; |
| data->REG_ALARM = NCT6775_REG_ALARM; |
| break; |
| case nct6776: |
| data->in_num = 9; |
| |
| data->ALARM_BITS = NCT6776_ALARM_BITS; |
| |
| data->REG_CONFIG = NCT6775_REG_CONFIG; |
| data->REG_VBAT = NCT6775_REG_VBAT; |
| data->REG_VIN = NCT6775_REG_IN; |
| data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; |
| data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; |
| data->REG_ALARM = NCT6775_REG_ALARM; |
| break; |
| case nct6779: |
| data->in_num = 15; |
| |
| data->ALARM_BITS = NCT6779_ALARM_BITS; |
| |
| data->REG_CONFIG = NCT6775_REG_CONFIG; |
| data->REG_VBAT = NCT6775_REG_VBAT; |
| data->REG_VIN = NCT6779_REG_IN; |
| data->REG_IN_MINMAX[0] = NCT6775_REG_IN_MIN; |
| data->REG_IN_MINMAX[1] = NCT6775_REG_IN_MAX; |
| data->REG_ALARM = NCT6779_REG_ALARM; |
| break; |
| default: |
| return -ENODEV; |
| } |
| data->have_in = (1 << data->in_num) - 1; |
| |
| /* Initialize the chip */ |
| nct6775_init_device(data); |
| |
| data->vrm = vid_which_vrm(); |
| err = superio_enter(sio_data->sioreg); |
| if (err) |
| return err; |
| |
| /* |
| * Read VID value |
| * We can get the VID input values directly at logical device D 0xe3. |
| */ |
| superio_select(sio_data->sioreg, NCT6775_LD_VID); |
| data->vid = superio_inb(sio_data->sioreg, 0xe3); |
| superio_exit(sio_data->sioreg); |
| |
| err = device_create_file(dev, &dev_attr_cpu0_vid); |
| if (err) |
| return err; |
| |
| for (i = 0; i < data->in_num; i++) { |
| if (!(data->have_in & (1 << i))) |
| continue; |
| err = sysfs_create_group(&dev->kobj, &nct6775_group_in[i]); |
| if (err) |
| goto exit_remove; |
| } |
| |
| err = device_create_file(dev, &dev_attr_name); |
| if (err) |
| goto exit_remove; |
| |
| data->hwmon_dev = hwmon_device_register(dev); |
| if (IS_ERR(data->hwmon_dev)) { |
| err = PTR_ERR(data->hwmon_dev); |
| goto exit_remove; |
| } |
| |
| return 0; |
| |
| exit_remove: |
| nct6775_device_remove_files(dev); |
| return err; |
| } |
| |
| static int nct6775_remove(struct platform_device *pdev) |
| { |
| struct nct6775_data *data = platform_get_drvdata(pdev); |
| |
| hwmon_device_unregister(data->hwmon_dev); |
| nct6775_device_remove_files(&pdev->dev); |
| |
| return 0; |
| } |
| |
| static struct platform_driver nct6775_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = DRVNAME, |
| }, |
| .probe = nct6775_probe, |
| .remove = nct6775_remove, |
| }; |
| |
| /* nct6775_find() looks for a '627 in the Super-I/O config space */ |
| static int __init nct6775_find(int sioaddr, unsigned short *addr, |
| struct nct6775_sio_data *sio_data) |
| { |
| static const char sio_name_NCT6775[] __initconst = "NCT6775F"; |
| static const char sio_name_NCT6776[] __initconst = "NCT6776F"; |
| static const char sio_name_NCT6779[] __initconst = "NCT6779D"; |
| |
| u16 val; |
| const char *sio_name; |
| int err; |
| |
| err = superio_enter(sioaddr); |
| if (err) |
| return err; |
| |
| if (force_id) |
| val = force_id; |
| else |
| val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) |
| | superio_inb(sioaddr, SIO_REG_DEVID + 1); |
| switch (val & SIO_ID_MASK) { |
| case SIO_NCT6775_ID: |
| sio_data->kind = nct6775; |
| sio_name = sio_name_NCT6775; |
| break; |
| case SIO_NCT6776_ID: |
| sio_data->kind = nct6776; |
| sio_name = sio_name_NCT6776; |
| break; |
| case SIO_NCT6779_ID: |
| sio_data->kind = nct6779; |
| sio_name = sio_name_NCT6779; |
| break; |
| default: |
| if (val != 0xffff) |
| pr_debug("unsupported chip ID: 0x%04x\n", val); |
| superio_exit(sioaddr); |
| return -ENODEV; |
| } |
| |
| /* We have a known chip, find the HWM I/O address */ |
| superio_select(sioaddr, NCT6775_LD_HWM); |
| val = (superio_inb(sioaddr, SIO_REG_ADDR) << 8) |
| | superio_inb(sioaddr, SIO_REG_ADDR + 1); |
| *addr = val & IOREGION_ALIGNMENT; |
| if (*addr == 0) { |
| pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); |
| superio_exit(sioaddr); |
| return -ENODEV; |
| } |
| |
| /* Activate logical device if needed */ |
| val = superio_inb(sioaddr, SIO_REG_ENABLE); |
| if (!(val & 0x01)) { |
| pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); |
| superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); |
| } |
| |
| superio_exit(sioaddr); |
| pr_info("Found %s chip at %#x\n", sio_name, *addr); |
| sio_data->sioreg = sioaddr; |
| |
| return 0; |
| } |
| |
| /* |
| * when Super-I/O functions move to a separate file, the Super-I/O |
| * bus will manage the lifetime of the device and this module will only keep |
| * track of the nct6775 driver. But since we platform_device_alloc(), we |
| * must keep track of the device |
| */ |
| static struct platform_device *pdev; |
| |
| static int __init sensors_nct6775_init(void) |
| { |
| int err; |
| unsigned short address; |
| struct resource res; |
| struct nct6775_sio_data sio_data; |
| |
| /* |
| * initialize sio_data->kind and sio_data->sioreg. |
| * |
| * when Super-I/O functions move to a separate file, the Super-I/O |
| * driver will probe 0x2e and 0x4e and auto-detect the presence of a |
| * nct6775 hardware monitor, and call probe() |
| */ |
| if (nct6775_find(0x2e, &address, &sio_data) && |
| nct6775_find(0x4e, &address, &sio_data)) |
| return -ENODEV; |
| |
| err = platform_driver_register(&nct6775_driver); |
| if (err) |
| goto exit; |
| |
| pdev = platform_device_alloc(DRVNAME, address); |
| if (!pdev) { |
| err = -ENOMEM; |
| pr_err("Device allocation failed\n"); |
| goto exit_unregister; |
| } |
| |
| err = platform_device_add_data(pdev, &sio_data, |
| sizeof(struct nct6775_sio_data)); |
| if (err) { |
| pr_err("Platform data allocation failed\n"); |
| goto exit_device_put; |
| } |
| |
| memset(&res, 0, sizeof(res)); |
| res.name = DRVNAME; |
| res.start = address + IOREGION_OFFSET; |
| res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; |
| res.flags = IORESOURCE_IO; |
| |
| err = acpi_check_resource_conflict(&res); |
| if (err) |
| goto exit_device_put; |
| |
| err = platform_device_add_resources(pdev, &res, 1); |
| if (err) { |
| pr_err("Device resource addition failed (%d)\n", err); |
| goto exit_device_put; |
| } |
| |
| /* platform_device_add calls probe() */ |
| err = platform_device_add(pdev); |
| if (err) { |
| pr_err("Device addition failed (%d)\n", err); |
| goto exit_device_put; |
| } |
| |
| return 0; |
| |
| exit_device_put: |
| platform_device_put(pdev); |
| exit_unregister: |
| platform_driver_unregister(&nct6775_driver); |
| exit: |
| return err; |
| } |
| |
| static void __exit sensors_nct6775_exit(void) |
| { |
| platform_device_unregister(pdev); |
| platform_driver_unregister(&nct6775_driver); |
| } |
| |
| MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); |
| MODULE_DESCRIPTION("NCT6775F/NCT6776F/NCT6779D driver"); |
| MODULE_LICENSE("GPL"); |
| |
| module_init(sensors_nct6775_init); |
| module_exit(sensors_nct6775_exit); |