| /* | |
| * nct6683 - Driver for the hardware monitoring functionality of | |
| * Nuvoton NCT6683D eSIO | |
| * | |
| * Copyright (C) 2013 Guenter Roeck <linux@roeck-us.net> | |
| * | |
| * Derived from nct6775 driver | |
| * Copyright (C) 2012, 2013 Guenter Roeck <linux@roeck-us.net> | |
| * | |
| * 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. | |
| * | |
| * Supports the following chips: | |
| * | |
| * Chip #vin #fan #pwm #temp chip ID | |
| * nct6683d 21(1) 16 8 32(1) 0xc730 | |
| * | |
| * Notes: | |
| * (1) Total number of vin and temp inputs is 32. | |
| */ | |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
| #include <linux/acpi.h> | |
| #include <linux/delay.h> | |
| #include <linux/err.h> | |
| #include <linux/init.h> | |
| #include <linux/io.h> | |
| #include <linux/jiffies.h> | |
| #include <linux/hwmon.h> | |
| #include <linux/hwmon-sysfs.h> | |
| #include <linux/module.h> | |
| #include <linux/mutex.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/slab.h> | |
| enum kinds { nct6683 }; | |
| static bool force; | |
| module_param(force, bool, 0); | |
| MODULE_PARM_DESC(force, "Set to one to enable support for unknown vendors"); | |
| static const char * const nct6683_device_names[] = { | |
| "nct6683", | |
| }; | |
| static const char * const nct6683_chip_names[] = { | |
| "NCT6683D", | |
| }; | |
| #define DRVNAME "nct6683" | |
| /* | |
| * Super-I/O constants and functions | |
| */ | |
| #define NCT6683_LD_ACPI 0x0a | |
| #define NCT6683_LD_HWM 0x0b | |
| #define NCT6683_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_NCT6681_ID 0xb270 /* for later */ | |
| #define SIO_NCT6683_ID 0xc730 | |
| #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 4 /* Use EC port 1 */ | |
| #define IOREGION_LENGTH 4 | |
| #define EC_PAGE_REG 0 | |
| #define EC_INDEX_REG 1 | |
| #define EC_DATA_REG 2 | |
| #define EC_EVENT_REG 3 | |
| /* Common and NCT6683 specific data */ | |
| #define NCT6683_NUM_REG_MON 32 | |
| #define NCT6683_NUM_REG_FAN 16 | |
| #define NCT6683_NUM_REG_PWM 8 | |
| #define NCT6683_REG_MON(x) (0x100 + (x) * 2) | |
| #define NCT6683_REG_FAN_RPM(x) (0x140 + (x) * 2) | |
| #define NCT6683_REG_PWM(x) (0x160 + (x)) | |
| #define NCT6683_REG_PWM_WRITE(x) (0xa28 + (x)) | |
| #define NCT6683_REG_MON_STS(x) (0x174 + (x)) | |
| #define NCT6683_REG_IDLE(x) (0x178 + (x)) | |
| #define NCT6683_REG_FAN_STS(x) (0x17c + (x)) | |
| #define NCT6683_REG_FAN_ERRSTS 0x17e | |
| #define NCT6683_REG_FAN_INITSTS 0x17f | |
| #define NCT6683_HWM_CFG 0x180 | |
| #define NCT6683_REG_MON_CFG(x) (0x1a0 + (x)) | |
| #define NCT6683_REG_FANIN_CFG(x) (0x1c0 + (x)) | |
| #define NCT6683_REG_FANOUT_CFG(x) (0x1d0 + (x)) | |
| #define NCT6683_REG_INTEL_TEMP_MAX(x) (0x901 + (x) * 16) | |
| #define NCT6683_REG_INTEL_TEMP_CRIT(x) (0x90d + (x) * 16) | |
| #define NCT6683_REG_TEMP_HYST(x) (0x330 + (x)) /* 8 bit */ | |
| #define NCT6683_REG_TEMP_MAX(x) (0x350 + (x)) /* 8 bit */ | |
| #define NCT6683_REG_MON_HIGH(x) (0x370 + (x) * 2) /* 8 bit */ | |
| #define NCT6683_REG_MON_LOW(x) (0x371 + (x) * 2) /* 8 bit */ | |
| #define NCT6683_REG_FAN_MIN(x) (0x3b8 + (x) * 2) /* 16 bit */ | |
| #define NCT6683_REG_FAN_CFG_CTRL 0xa01 | |
| #define NCT6683_FAN_CFG_REQ 0x80 | |
| #define NCT6683_FAN_CFG_DONE 0x40 | |
| #define NCT6683_REG_CUSTOMER_ID 0x602 | |
| #define NCT6683_CUSTOMER_ID_INTEL 0x805 | |
| #define NCT6683_CUSTOMER_ID_MITAC 0xa0e | |
| #define NCT6683_REG_BUILD_YEAR 0x604 | |
| #define NCT6683_REG_BUILD_MONTH 0x605 | |
| #define NCT6683_REG_BUILD_DAY 0x606 | |
| #define NCT6683_REG_SERIAL 0x607 | |
| #define NCT6683_REG_VERSION_HI 0x608 | |
| #define NCT6683_REG_VERSION_LO 0x609 | |
| #define NCT6683_REG_CR_CASEOPEN 0xe8 | |
| #define NCT6683_CR_CASEOPEN_MASK (1 << 7) | |
| #define NCT6683_REG_CR_BEEP 0xe0 | |
| #define NCT6683_CR_BEEP_MASK (1 << 6) | |
| static const char *const nct6683_mon_label[] = { | |
| NULL, /* disabled */ | |
| "Local", | |
| "Diode 0 (curr)", | |
| "Diode 1 (curr)", | |
| "Diode 2 (curr)", | |
| "Diode 0 (volt)", | |
| "Diode 1 (volt)", | |
| "Diode 2 (volt)", | |
| "Thermistor 14", | |
| "Thermistor 15", | |
| "Thermistor 16", | |
| "Thermistor 0", | |
| "Thermistor 1", | |
| "Thermistor 2", | |
| "Thermistor 3", | |
| "Thermistor 4", | |
| "Thermistor 5", /* 0x10 */ | |
| "Thermistor 6", | |
| "Thermistor 7", | |
| "Thermistor 8", | |
| "Thermistor 9", | |
| "Thermistor 10", | |
| "Thermistor 11", | |
| "Thermistor 12", | |
| "Thermistor 13", | |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, | |
| "PECI 0.0", /* 0x20 */ | |
| "PECI 1.0", | |
| "PECI 2.0", | |
| "PECI 3.0", | |
| "PECI 0.1", | |
| "PECI 1.1", | |
| "PECI 2.1", | |
| "PECI 3.1", | |
| "PECI DIMM 0", | |
| "PECI DIMM 1", | |
| "PECI DIMM 2", | |
| "PECI DIMM 3", | |
| NULL, NULL, NULL, NULL, | |
| "PCH CPU", /* 0x30 */ | |
| "PCH CHIP", | |
| "PCH CHIP CPU MAX", | |
| "PCH MCH", | |
| "PCH DIMM 0", | |
| "PCH DIMM 1", | |
| "PCH DIMM 2", | |
| "PCH DIMM 3", | |
| "SMBus 0", | |
| "SMBus 1", | |
| "SMBus 2", | |
| "SMBus 3", | |
| "SMBus 4", | |
| "SMBus 5", | |
| "DIMM 0", | |
| "DIMM 1", | |
| "DIMM 2", /* 0x40 */ | |
| "DIMM 3", | |
| "AMD TSI Addr 90h", | |
| "AMD TSI Addr 92h", | |
| "AMD TSI Addr 94h", | |
| "AMD TSI Addr 96h", | |
| "AMD TSI Addr 98h", | |
| "AMD TSI Addr 9ah", | |
| "AMD TSI Addr 9ch", | |
| "AMD TSI Addr 9dh", | |
| NULL, NULL, NULL, NULL, NULL, NULL, | |
| "Virtual 0", /* 0x50 */ | |
| "Virtual 1", | |
| "Virtual 2", | |
| "Virtual 3", | |
| "Virtual 4", | |
| "Virtual 5", | |
| "Virtual 6", | |
| "Virtual 7", | |
| NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, | |
| "VCC", /* 0x60 voltage sensors */ | |
| "VSB", | |
| "AVSB", | |
| "VTT", | |
| "VBAT", | |
| "VREF", | |
| "VIN0", | |
| "VIN1", | |
| "VIN2", | |
| "VIN3", | |
| "VIN4", | |
| "VIN5", | |
| "VIN6", | |
| "VIN7", | |
| "VIN8", | |
| "VIN9", | |
| "VIN10", | |
| "VIN11", | |
| "VIN12", | |
| "VIN13", | |
| "VIN14", | |
| "VIN15", | |
| "VIN16", | |
| }; | |
| #define NUM_MON_LABELS ARRAY_SIZE(nct6683_mon_label) | |
| #define MON_VOLTAGE_START 0x60 | |
| /* ------------------------------------------------------- */ | |
| struct nct6683_data { | |
| int addr; /* IO base of EC space */ | |
| int sioreg; /* SIO register */ | |
| enum kinds kind; | |
| u16 customer_id; | |
| struct device *hwmon_dev; | |
| const struct attribute_group *groups[6]; | |
| int temp_num; /* number of temperature attributes */ | |
| u8 temp_index[NCT6683_NUM_REG_MON]; | |
| u8 temp_src[NCT6683_NUM_REG_MON]; | |
| u8 in_num; /* number of voltage attributes */ | |
| u8 in_index[NCT6683_NUM_REG_MON]; | |
| u8 in_src[NCT6683_NUM_REG_MON]; | |
| struct mutex update_lock; /* used to protect sensor updates */ | |
| bool valid; /* true if following fields are valid */ | |
| unsigned long last_updated; /* In jiffies */ | |
| /* Voltage attribute values */ | |
| u8 in[3][NCT6683_NUM_REG_MON]; /* [0]=in, [1]=in_max, [2]=in_min */ | |
| /* Temperature attribute values */ | |
| s16 temp_in[NCT6683_NUM_REG_MON]; | |
| s8 temp[4][NCT6683_NUM_REG_MON];/* [0]=min, [1]=max, [2]=hyst, | |
| * [3]=crit | |
| */ | |
| /* Fan attribute values */ | |
| unsigned int rpm[NCT6683_NUM_REG_FAN]; | |
| u16 fan_min[NCT6683_NUM_REG_FAN]; | |
| u8 fanin_cfg[NCT6683_NUM_REG_FAN]; | |
| u8 fanout_cfg[NCT6683_NUM_REG_FAN]; | |
| u16 have_fan; /* some fan inputs can be disabled */ | |
| u8 have_pwm; | |
| u8 pwm[NCT6683_NUM_REG_PWM]; | |
| #ifdef CONFIG_PM | |
| /* Remember extra register values over suspend/resume */ | |
| u8 hwm_cfg; | |
| #endif | |
| }; | |
| struct nct6683_sio_data { | |
| int sioreg; | |
| enum kinds kind; | |
| }; | |
| struct sensor_device_template { | |
| struct device_attribute dev_attr; | |
| union { | |
| struct { | |
| u8 nr; | |
| u8 index; | |
| } s; | |
| int index; | |
| } u; | |
| bool s2; /* true if both index and nr are used */ | |
| }; | |
| struct sensor_device_attr_u { | |
| union { | |
| struct sensor_device_attribute a1; | |
| struct sensor_device_attribute_2 a2; | |
| } u; | |
| char name[32]; | |
| }; | |
| #define __TEMPLATE_ATTR(_template, _mode, _show, _store) { \ | |
| .attr = {.name = _template, .mode = _mode }, \ | |
| .show = _show, \ | |
| .store = _store, \ | |
| } | |
| #define SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, _index) \ | |
| { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ | |
| .u.index = _index, \ | |
| .s2 = false } | |
| #define SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ | |
| _nr, _index) \ | |
| { .dev_attr = __TEMPLATE_ATTR(_template, _mode, _show, _store), \ | |
| .u.s.index = _index, \ | |
| .u.s.nr = _nr, \ | |
| .s2 = true } | |
| #define SENSOR_TEMPLATE(_name, _template, _mode, _show, _store, _index) \ | |
| static struct sensor_device_template sensor_dev_template_##_name \ | |
| = SENSOR_DEVICE_TEMPLATE(_template, _mode, _show, _store, \ | |
| _index) | |
| #define SENSOR_TEMPLATE_2(_name, _template, _mode, _show, _store, \ | |
| _nr, _index) \ | |
| static struct sensor_device_template sensor_dev_template_##_name \ | |
| = SENSOR_DEVICE_TEMPLATE_2(_template, _mode, _show, _store, \ | |
| _nr, _index) | |
| struct sensor_template_group { | |
| struct sensor_device_template **templates; | |
| umode_t (*is_visible)(struct kobject *, struct attribute *, int); | |
| int base; | |
| }; | |
| static struct attribute_group * | |
| nct6683_create_attr_group(struct device *dev, | |
| const struct sensor_template_group *tg, | |
| int repeat) | |
| { | |
| struct sensor_device_attribute_2 *a2; | |
| struct sensor_device_attribute *a; | |
| struct sensor_device_template **t; | |
| struct sensor_device_attr_u *su; | |
| struct attribute_group *group; | |
| struct attribute **attrs; | |
| int i, j, count; | |
| if (repeat <= 0) | |
| return ERR_PTR(-EINVAL); | |
| t = tg->templates; | |
| for (count = 0; *t; t++, count++) | |
| ; | |
| if (count == 0) | |
| return ERR_PTR(-EINVAL); | |
| group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); | |
| if (group == NULL) | |
| return ERR_PTR(-ENOMEM); | |
| attrs = devm_kzalloc(dev, sizeof(*attrs) * (repeat * count + 1), | |
| GFP_KERNEL); | |
| if (attrs == NULL) | |
| return ERR_PTR(-ENOMEM); | |
| su = devm_kzalloc(dev, sizeof(*su) * repeat * count, | |
| GFP_KERNEL); | |
| if (su == NULL) | |
| return ERR_PTR(-ENOMEM); | |
| group->attrs = attrs; | |
| group->is_visible = tg->is_visible; | |
| for (i = 0; i < repeat; i++) { | |
| t = tg->templates; | |
| for (j = 0; *t != NULL; j++) { | |
| snprintf(su->name, sizeof(su->name), | |
| (*t)->dev_attr.attr.name, tg->base + i); | |
| if ((*t)->s2) { | |
| a2 = &su->u.a2; | |
| sysfs_attr_init(&a2->dev_attr.attr); | |
| a2->dev_attr.attr.name = su->name; | |
| a2->nr = (*t)->u.s.nr + i; | |
| a2->index = (*t)->u.s.index; | |
| a2->dev_attr.attr.mode = | |
| (*t)->dev_attr.attr.mode; | |
| a2->dev_attr.show = (*t)->dev_attr.show; | |
| a2->dev_attr.store = (*t)->dev_attr.store; | |
| *attrs = &a2->dev_attr.attr; | |
| } else { | |
| a = &su->u.a1; | |
| sysfs_attr_init(&a->dev_attr.attr); | |
| a->dev_attr.attr.name = su->name; | |
| a->index = (*t)->u.index + i; | |
| a->dev_attr.attr.mode = | |
| (*t)->dev_attr.attr.mode; | |
| a->dev_attr.show = (*t)->dev_attr.show; | |
| a->dev_attr.store = (*t)->dev_attr.store; | |
| *attrs = &a->dev_attr.attr; | |
| } | |
| attrs++; | |
| su++; | |
| t++; | |
| } | |
| } | |
| return group; | |
| } | |
| /* LSB is 16 mV, except for the following sources, where it is 32 mV */ | |
| #define MON_SRC_VCC 0x60 | |
| #define MON_SRC_VSB 0x61 | |
| #define MON_SRC_AVSB 0x62 | |
| #define MON_SRC_VBAT 0x64 | |
| static inline long in_from_reg(u16 reg, u8 src) | |
| { | |
| int scale = 16; | |
| if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || | |
| src == MON_SRC_VBAT) | |
| scale <<= 1; | |
| return reg * scale; | |
| } | |
| static inline u16 in_to_reg(u32 val, u8 src) | |
| { | |
| int scale = 16; | |
| if (src == MON_SRC_VCC || src == MON_SRC_VSB || src == MON_SRC_AVSB || | |
| src == MON_SRC_VBAT) | |
| scale <<= 1; | |
| return clamp_val(DIV_ROUND_CLOSEST(val, scale), 0, 127); | |
| } | |
| static u16 nct6683_read(struct nct6683_data *data, u16 reg) | |
| { | |
| int res; | |
| outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ | |
| outb_p(reg >> 8, data->addr + EC_PAGE_REG); | |
| outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | |
| res = inb_p(data->addr + EC_DATA_REG); | |
| return res; | |
| } | |
| static u16 nct6683_read16(struct nct6683_data *data, u16 reg) | |
| { | |
| return (nct6683_read(data, reg) << 8) | nct6683_read(data, reg + 1); | |
| } | |
| static void nct6683_write(struct nct6683_data *data, u16 reg, u16 value) | |
| { | |
| outb_p(0xff, data->addr + EC_PAGE_REG); /* unlock */ | |
| outb_p(reg >> 8, data->addr + EC_PAGE_REG); | |
| outb_p(reg & 0xff, data->addr + EC_INDEX_REG); | |
| outb_p(value & 0xff, data->addr + EC_DATA_REG); | |
| } | |
| static int get_in_reg(struct nct6683_data *data, int nr, int index) | |
| { | |
| int ch = data->in_index[index]; | |
| int reg = -EINVAL; | |
| switch (nr) { | |
| case 0: | |
| reg = NCT6683_REG_MON(ch); | |
| break; | |
| case 1: | |
| if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | |
| reg = NCT6683_REG_MON_LOW(ch); | |
| break; | |
| case 2: | |
| if (data->customer_id != NCT6683_CUSTOMER_ID_INTEL) | |
| reg = NCT6683_REG_MON_HIGH(ch); | |
| break; | |
| default: | |
| break; | |
| } | |
| return reg; | |
| } | |
| static int get_temp_reg(struct nct6683_data *data, int nr, int index) | |
| { | |
| int ch = data->temp_index[index]; | |
| int reg = -EINVAL; | |
| switch (data->customer_id) { | |
| case NCT6683_CUSTOMER_ID_INTEL: | |
| switch (nr) { | |
| default: | |
| case 1: /* max */ | |
| reg = NCT6683_REG_INTEL_TEMP_MAX(ch); | |
| break; | |
| case 3: /* crit */ | |
| reg = NCT6683_REG_INTEL_TEMP_CRIT(ch); | |
| break; | |
| } | |
| break; | |
| case NCT6683_CUSTOMER_ID_MITAC: | |
| default: | |
| switch (nr) { | |
| default: | |
| case 0: /* min */ | |
| reg = NCT6683_REG_MON_LOW(ch); | |
| break; | |
| case 1: /* max */ | |
| reg = NCT6683_REG_TEMP_MAX(ch); | |
| break; | |
| case 2: /* hyst */ | |
| reg = NCT6683_REG_TEMP_HYST(ch); | |
| break; | |
| case 3: /* crit */ | |
| reg = NCT6683_REG_MON_HIGH(ch); | |
| break; | |
| } | |
| break; | |
| } | |
| return reg; | |
| } | |
| static void nct6683_update_pwm(struct device *dev) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int i; | |
| for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { | |
| if (!(data->have_pwm & (1 << i))) | |
| continue; | |
| data->pwm[i] = nct6683_read(data, NCT6683_REG_PWM(i)); | |
| } | |
| } | |
| static struct nct6683_data *nct6683_update_device(struct device *dev) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int i, j; | |
| mutex_lock(&data->update_lock); | |
| if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { | |
| /* Measured voltages and limits */ | |
| for (i = 0; i < data->in_num; i++) { | |
| for (j = 0; j < 3; j++) { | |
| int reg = get_in_reg(data, j, i); | |
| if (reg >= 0) | |
| data->in[j][i] = | |
| nct6683_read(data, reg); | |
| } | |
| } | |
| /* Measured temperatures and limits */ | |
| for (i = 0; i < data->temp_num; i++) { | |
| u8 ch = data->temp_index[i]; | |
| data->temp_in[i] = nct6683_read16(data, | |
| NCT6683_REG_MON(ch)); | |
| for (j = 0; j < 4; j++) { | |
| int reg = get_temp_reg(data, j, i); | |
| if (reg >= 0) | |
| data->temp[j][i] = | |
| nct6683_read(data, reg); | |
| } | |
| } | |
| /* Measured fan speeds and limits */ | |
| for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { | |
| if (!(data->have_fan & (1 << i))) | |
| continue; | |
| data->rpm[i] = nct6683_read16(data, | |
| NCT6683_REG_FAN_RPM(i)); | |
| data->fan_min[i] = nct6683_read16(data, | |
| NCT6683_REG_FAN_MIN(i)); | |
| } | |
| nct6683_update_pwm(dev); | |
| data->last_updated = jiffies; | |
| data->valid = true; | |
| } | |
| mutex_unlock(&data->update_lock); | |
| return data; | |
| } | |
| /* | |
| * Sysfs callback functions | |
| */ | |
| static ssize_t | |
| show_in_label(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int nr = sattr->index; | |
| return sprintf(buf, "%s\n", nct6683_mon_label[data->in_src[nr]]); | |
| } | |
| static ssize_t | |
| show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int index = sattr->index; | |
| int nr = sattr->nr; | |
| return sprintf(buf, "%ld\n", | |
| in_from_reg(data->in[index][nr], data->in_index[index])); | |
| } | |
| static umode_t nct6683_in_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int index) | |
| { | |
| struct device *dev = container_of(kobj, struct device, kobj); | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int nr = index % 4; /* attribute */ | |
| /* | |
| * Voltage limits exist for Intel boards, | |
| * but register location and encoding is unknown | |
| */ | |
| if ((nr == 2 || nr == 3) && | |
| data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | |
| return 0; | |
| return attr->mode; | |
| } | |
| SENSOR_TEMPLATE(in_label, "in%d_label", S_IRUGO, show_in_label, NULL, 0); | |
| SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); | |
| SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IRUGO, show_in_reg, NULL, 0, 1); | |
| SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IRUGO, show_in_reg, NULL, 0, 2); | |
| static struct sensor_device_template *nct6683_attributes_in_template[] = { | |
| &sensor_dev_template_in_label, | |
| &sensor_dev_template_in_input, | |
| &sensor_dev_template_in_min, | |
| &sensor_dev_template_in_max, | |
| NULL | |
| }; | |
| static const struct sensor_template_group nct6683_in_template_group = { | |
| .templates = nct6683_attributes_in_template, | |
| .is_visible = nct6683_in_is_visible, | |
| }; | |
| static ssize_t | |
| show_fan(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| return sprintf(buf, "%d\n", data->rpm[sattr->index]); | |
| } | |
| static ssize_t | |
| show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| int nr = sattr->index; | |
| return sprintf(buf, "%d\n", data->fan_min[nr]); | |
| } | |
| static ssize_t | |
| show_fan_pulses(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| return sprintf(buf, "%d\n", | |
| ((data->fanin_cfg[sattr->index] >> 5) & 0x03) + 1); | |
| } | |
| static umode_t nct6683_fan_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int index) | |
| { | |
| struct device *dev = container_of(kobj, struct device, kobj); | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int fan = index / 3; /* fan index */ | |
| int nr = index % 3; /* attribute index */ | |
| if (!(data->have_fan & (1 << fan))) | |
| return 0; | |
| /* | |
| * Intel may have minimum fan speed limits, | |
| * but register location and encoding are unknown. | |
| */ | |
| if (nr == 2 && data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | |
| return 0; | |
| return attr->mode; | |
| } | |
| SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); | |
| SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IRUGO, show_fan_pulses, NULL, 0); | |
| SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IRUGO, show_fan_min, NULL, 0); | |
| /* | |
| * nct6683_fan_is_visible uses the index into the following array | |
| * to determine if attributes should be created or not. | |
| * Any change in order or content must be matched. | |
| */ | |
| static struct sensor_device_template *nct6683_attributes_fan_template[] = { | |
| &sensor_dev_template_fan_input, | |
| &sensor_dev_template_fan_pulses, | |
| &sensor_dev_template_fan_min, | |
| NULL | |
| }; | |
| static const struct sensor_template_group nct6683_fan_template_group = { | |
| .templates = nct6683_attributes_fan_template, | |
| .is_visible = nct6683_fan_is_visible, | |
| .base = 1, | |
| }; | |
| static ssize_t | |
| show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int nr = sattr->index; | |
| return sprintf(buf, "%s\n", nct6683_mon_label[data->temp_src[nr]]); | |
| } | |
| static ssize_t | |
| show_temp8(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int index = sattr->index; | |
| int nr = sattr->nr; | |
| return sprintf(buf, "%d\n", data->temp[index][nr] * 1000); | |
| } | |
| static ssize_t | |
| show_temp_hyst(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int nr = sattr->index; | |
| int temp = data->temp[1][nr] - data->temp[2][nr]; | |
| return sprintf(buf, "%d\n", temp * 1000); | |
| } | |
| static ssize_t | |
| show_temp16(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| int index = sattr->index; | |
| return sprintf(buf, "%d\n", (data->temp_in[index] / 128) * 500); | |
| } | |
| /* | |
| * Temperature sensor type is determined by temperature source | |
| * and can not be modified. | |
| * 0x02..0x07: Thermal diode | |
| * 0x08..0x18: Thermistor | |
| * 0x20..0x2b: Intel PECI | |
| * 0x42..0x49: AMD TSI | |
| * Others are unspecified (not visible) | |
| */ | |
| static int get_temp_type(u8 src) | |
| { | |
| if (src >= 0x02 && src <= 0x07) | |
| return 3; /* thermal diode */ | |
| else if (src >= 0x08 && src <= 0x18) | |
| return 4; /* thermistor */ | |
| else if (src >= 0x20 && src <= 0x2b) | |
| return 6; /* PECI */ | |
| else if (src >= 0x42 && src <= 0x49) | |
| return 5; | |
| return 0; | |
| } | |
| static ssize_t | |
| show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); | |
| int nr = sattr->index; | |
| return sprintf(buf, "%d\n", get_temp_type(data->temp_src[nr])); | |
| } | |
| static umode_t nct6683_temp_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int index) | |
| { | |
| struct device *dev = container_of(kobj, struct device, kobj); | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int temp = index / 7; /* temp index */ | |
| int nr = index % 7; /* attribute index */ | |
| /* | |
| * Intel does not have low temperature limits or temperature hysteresis | |
| * registers, or at least register location and encoding is unknown. | |
| */ | |
| if ((nr == 2 || nr == 4) && | |
| data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | |
| return 0; | |
| if (nr == 6 && get_temp_type(data->temp_src[temp]) == 0) | |
| return 0; /* type */ | |
| return attr->mode; | |
| } | |
| SENSOR_TEMPLATE(temp_input, "temp%d_input", S_IRUGO, show_temp16, NULL, 0); | |
| SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); | |
| SENSOR_TEMPLATE_2(temp_min, "temp%d_min", S_IRUGO, show_temp8, NULL, 0, 0); | |
| SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO, show_temp8, NULL, 0, 1); | |
| SENSOR_TEMPLATE(temp_max_hyst, "temp%d_max_hyst", S_IRUGO, show_temp_hyst, NULL, | |
| 0); | |
| SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO, show_temp8, NULL, 0, 3); | |
| SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO, show_temp_type, NULL, 0); | |
| /* | |
| * nct6683_temp_is_visible uses the index into the following array | |
| * to determine if attributes should be created or not. | |
| * Any change in order or content must be matched. | |
| */ | |
| static struct sensor_device_template *nct6683_attributes_temp_template[] = { | |
| &sensor_dev_template_temp_input, | |
| &sensor_dev_template_temp_label, | |
| &sensor_dev_template_temp_min, /* 2 */ | |
| &sensor_dev_template_temp_max, /* 3 */ | |
| &sensor_dev_template_temp_max_hyst, /* 4 */ | |
| &sensor_dev_template_temp_crit, /* 5 */ | |
| &sensor_dev_template_temp_type, /* 6 */ | |
| NULL | |
| }; | |
| static const struct sensor_template_group nct6683_temp_template_group = { | |
| .templates = nct6683_attributes_temp_template, | |
| .is_visible = nct6683_temp_is_visible, | |
| .base = 1, | |
| }; | |
| static ssize_t | |
| show_pwm(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | |
| int index = sattr->index; | |
| return sprintf(buf, "%d\n", data->pwm[index]); | |
| } | |
| static ssize_t | |
| store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, | |
| size_t count) | |
| { | |
| struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int index = sattr->index; | |
| unsigned long val; | |
| if (kstrtoul(buf, 10, &val) || val > 255) | |
| return -EINVAL; | |
| mutex_lock(&data->update_lock); | |
| nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_REQ); | |
| usleep_range(1000, 2000); | |
| nct6683_write(data, NCT6683_REG_PWM_WRITE(index), val); | |
| nct6683_write(data, NCT6683_REG_FAN_CFG_CTRL, NCT6683_FAN_CFG_DONE); | |
| mutex_unlock(&data->update_lock); | |
| return count; | |
| } | |
| SENSOR_TEMPLATE(pwm, "pwm%d", S_IRUGO, show_pwm, store_pwm, 0); | |
| static umode_t nct6683_pwm_is_visible(struct kobject *kobj, | |
| struct attribute *attr, int index) | |
| { | |
| struct device *dev = container_of(kobj, struct device, kobj); | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int pwm = index; /* pwm index */ | |
| if (!(data->have_pwm & (1 << pwm))) | |
| return 0; | |
| /* Only update pwm values for Mitac boards */ | |
| if (data->customer_id == NCT6683_CUSTOMER_ID_MITAC) | |
| return attr->mode | S_IWUSR; | |
| return attr->mode; | |
| } | |
| static struct sensor_device_template *nct6683_attributes_pwm_template[] = { | |
| &sensor_dev_template_pwm, | |
| NULL | |
| }; | |
| static const struct sensor_template_group nct6683_pwm_template_group = { | |
| .templates = nct6683_attributes_pwm_template, | |
| .is_visible = nct6683_pwm_is_visible, | |
| .base = 1, | |
| }; | |
| static ssize_t | |
| show_global_beep(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int ret; | |
| u8 reg; | |
| mutex_lock(&data->update_lock); | |
| ret = superio_enter(data->sioreg); | |
| if (ret) | |
| goto error; | |
| superio_select(data->sioreg, NCT6683_LD_HWM); | |
| reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); | |
| superio_exit(data->sioreg); | |
| mutex_unlock(&data->update_lock); | |
| return sprintf(buf, "%u\n", !!(reg & NCT6683_CR_BEEP_MASK)); | |
| error: | |
| mutex_unlock(&data->update_lock); | |
| return ret; | |
| } | |
| static ssize_t | |
| store_global_beep(struct device *dev, struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| unsigned long val; | |
| u8 reg; | |
| int ret; | |
| if (kstrtoul(buf, 10, &val) || (val != 0 && val != 1)) | |
| return -EINVAL; | |
| mutex_lock(&data->update_lock); | |
| ret = superio_enter(data->sioreg); | |
| if (ret) { | |
| count = ret; | |
| goto error; | |
| } | |
| superio_select(data->sioreg, NCT6683_LD_HWM); | |
| reg = superio_inb(data->sioreg, NCT6683_REG_CR_BEEP); | |
| if (val) | |
| reg |= NCT6683_CR_BEEP_MASK; | |
| else | |
| reg &= ~NCT6683_CR_BEEP_MASK; | |
| superio_outb(data->sioreg, NCT6683_REG_CR_BEEP, reg); | |
| superio_exit(data->sioreg); | |
| error: | |
| mutex_unlock(&data->update_lock); | |
| return count; | |
| } | |
| /* Case open detection */ | |
| static ssize_t | |
| show_caseopen(struct device *dev, struct device_attribute *attr, char *buf) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| int ret; | |
| u8 reg; | |
| mutex_lock(&data->update_lock); | |
| ret = superio_enter(data->sioreg); | |
| if (ret) | |
| goto error; | |
| superio_select(data->sioreg, NCT6683_LD_ACPI); | |
| reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); | |
| superio_exit(data->sioreg); | |
| mutex_unlock(&data->update_lock); | |
| return sprintf(buf, "%u\n", !(reg & NCT6683_CR_CASEOPEN_MASK)); | |
| error: | |
| mutex_unlock(&data->update_lock); | |
| return ret; | |
| } | |
| static ssize_t | |
| clear_caseopen(struct device *dev, struct device_attribute *attr, | |
| const char *buf, size_t count) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| unsigned long val; | |
| u8 reg; | |
| int ret; | |
| if (kstrtoul(buf, 10, &val) || val != 0) | |
| return -EINVAL; | |
| mutex_lock(&data->update_lock); | |
| /* | |
| * Use CR registers to clear caseopen status. | |
| * Caseopen is activ low, clear by writing 1 into the register. | |
| */ | |
| ret = superio_enter(data->sioreg); | |
| if (ret) { | |
| count = ret; | |
| goto error; | |
| } | |
| superio_select(data->sioreg, NCT6683_LD_ACPI); | |
| reg = superio_inb(data->sioreg, NCT6683_REG_CR_CASEOPEN); | |
| reg |= NCT6683_CR_CASEOPEN_MASK; | |
| superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); | |
| reg &= ~NCT6683_CR_CASEOPEN_MASK; | |
| superio_outb(data->sioreg, NCT6683_REG_CR_CASEOPEN, reg); | |
| superio_exit(data->sioreg); | |
| data->valid = false; /* Force cache refresh */ | |
| error: | |
| mutex_unlock(&data->update_lock); | |
| return count; | |
| } | |
| static DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_caseopen, | |
| clear_caseopen); | |
| static DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_global_beep, | |
| store_global_beep); | |
| static struct attribute *nct6683_attributes_other[] = { | |
| &dev_attr_intrusion0_alarm.attr, | |
| &dev_attr_beep_enable.attr, | |
| NULL | |
| }; | |
| static const struct attribute_group nct6683_group_other = { | |
| .attrs = nct6683_attributes_other, | |
| }; | |
| /* Get the monitoring functions started */ | |
| static inline void nct6683_init_device(struct nct6683_data *data) | |
| { | |
| u8 tmp; | |
| /* Start hardware monitoring if needed */ | |
| tmp = nct6683_read(data, NCT6683_HWM_CFG); | |
| if (!(tmp & 0x80)) | |
| nct6683_write(data, NCT6683_HWM_CFG, tmp | 0x80); | |
| } | |
| /* | |
| * There are a total of 24 fan inputs. Each can be configured as input | |
| * or as output. A maximum of 16 inputs and 8 outputs is configurable. | |
| */ | |
| static void | |
| nct6683_setup_fans(struct nct6683_data *data) | |
| { | |
| int i; | |
| u8 reg; | |
| for (i = 0; i < NCT6683_NUM_REG_FAN; i++) { | |
| reg = nct6683_read(data, NCT6683_REG_FANIN_CFG(i)); | |
| if (reg & 0x80) | |
| data->have_fan |= 1 << i; | |
| data->fanin_cfg[i] = reg; | |
| } | |
| for (i = 0; i < NCT6683_NUM_REG_PWM; i++) { | |
| reg = nct6683_read(data, NCT6683_REG_FANOUT_CFG(i)); | |
| if (reg & 0x80) | |
| data->have_pwm |= 1 << i; | |
| data->fanout_cfg[i] = reg; | |
| } | |
| } | |
| /* | |
| * Translation from monitoring register to temperature and voltage attributes | |
| * ========================================================================== | |
| * | |
| * There are a total of 32 monitoring registers. Each can be assigned to either | |
| * a temperature or voltage monitoring source. | |
| * NCT6683_REG_MON_CFG(x) defines assignment for each monitoring source. | |
| * | |
| * Temperature and voltage attribute mapping is determined by walking through | |
| * the NCT6683_REG_MON_CFG registers. If the assigned source is | |
| * a temperature, temp_index[n] is set to the monitor register index, and | |
| * temp_src[n] is set to the temperature source. If the assigned source is | |
| * a voltage, the respective values are stored in in_index[] and in_src[], | |
| * respectively. | |
| */ | |
| static void nct6683_setup_sensors(struct nct6683_data *data) | |
| { | |
| u8 reg; | |
| int i; | |
| data->temp_num = 0; | |
| data->in_num = 0; | |
| for (i = 0; i < NCT6683_NUM_REG_MON; i++) { | |
| reg = nct6683_read(data, NCT6683_REG_MON_CFG(i)) & 0x7f; | |
| /* Ignore invalid assignments */ | |
| if (reg >= NUM_MON_LABELS) | |
| continue; | |
| /* Skip if disabled or reserved */ | |
| if (nct6683_mon_label[reg] == NULL) | |
| continue; | |
| if (reg < MON_VOLTAGE_START) { | |
| data->temp_index[data->temp_num] = i; | |
| data->temp_src[data->temp_num] = reg; | |
| data->temp_num++; | |
| } else { | |
| data->in_index[data->in_num] = i; | |
| data->in_src[data->in_num] = reg; | |
| data->in_num++; | |
| } | |
| } | |
| } | |
| static int nct6683_probe(struct platform_device *pdev) | |
| { | |
| struct device *dev = &pdev->dev; | |
| struct nct6683_sio_data *sio_data = dev->platform_data; | |
| struct attribute_group *group; | |
| struct nct6683_data *data; | |
| struct device *hwmon_dev; | |
| struct resource *res; | |
| int groups = 0; | |
| char build[16]; | |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); | |
| if (!devm_request_region(dev, res->start, IOREGION_LENGTH, DRVNAME)) | |
| return -EBUSY; | |
| data = devm_kzalloc(dev, sizeof(struct nct6683_data), GFP_KERNEL); | |
| if (!data) | |
| return -ENOMEM; | |
| data->kind = sio_data->kind; | |
| data->sioreg = sio_data->sioreg; | |
| data->addr = res->start; | |
| mutex_init(&data->update_lock); | |
| platform_set_drvdata(pdev, data); | |
| data->customer_id = nct6683_read16(data, NCT6683_REG_CUSTOMER_ID); | |
| /* By default only instantiate driver if the customer ID is known */ | |
| switch (data->customer_id) { | |
| case NCT6683_CUSTOMER_ID_INTEL: | |
| break; | |
| case NCT6683_CUSTOMER_ID_MITAC: | |
| break; | |
| default: | |
| if (!force) | |
| return -ENODEV; | |
| } | |
| nct6683_init_device(data); | |
| nct6683_setup_fans(data); | |
| nct6683_setup_sensors(data); | |
| /* Register sysfs hooks */ | |
| if (data->have_pwm) { | |
| group = nct6683_create_attr_group(dev, | |
| &nct6683_pwm_template_group, | |
| fls(data->have_pwm)); | |
| if (IS_ERR(group)) | |
| return PTR_ERR(group); | |
| data->groups[groups++] = group; | |
| } | |
| if (data->in_num) { | |
| group = nct6683_create_attr_group(dev, | |
| &nct6683_in_template_group, | |
| data->in_num); | |
| if (IS_ERR(group)) | |
| return PTR_ERR(group); | |
| data->groups[groups++] = group; | |
| } | |
| if (data->have_fan) { | |
| group = nct6683_create_attr_group(dev, | |
| &nct6683_fan_template_group, | |
| fls(data->have_fan)); | |
| if (IS_ERR(group)) | |
| return PTR_ERR(group); | |
| data->groups[groups++] = group; | |
| } | |
| if (data->temp_num) { | |
| group = nct6683_create_attr_group(dev, | |
| &nct6683_temp_template_group, | |
| data->temp_num); | |
| if (IS_ERR(group)) | |
| return PTR_ERR(group); | |
| data->groups[groups++] = group; | |
| } | |
| data->groups[groups++] = &nct6683_group_other; | |
| if (data->customer_id == NCT6683_CUSTOMER_ID_INTEL) | |
| scnprintf(build, sizeof(build), "%02x/%02x/%02x", | |
| nct6683_read(data, NCT6683_REG_BUILD_MONTH), | |
| nct6683_read(data, NCT6683_REG_BUILD_DAY), | |
| nct6683_read(data, NCT6683_REG_BUILD_YEAR)); | |
| else | |
| scnprintf(build, sizeof(build), "%02d/%02d/%02d", | |
| nct6683_read(data, NCT6683_REG_BUILD_MONTH), | |
| nct6683_read(data, NCT6683_REG_BUILD_DAY), | |
| nct6683_read(data, NCT6683_REG_BUILD_YEAR)); | |
| dev_info(dev, "%s EC firmware version %d.%d build %s\n", | |
| nct6683_chip_names[data->kind], | |
| nct6683_read(data, NCT6683_REG_VERSION_HI), | |
| nct6683_read(data, NCT6683_REG_VERSION_LO), | |
| build); | |
| hwmon_dev = devm_hwmon_device_register_with_groups(dev, | |
| nct6683_device_names[data->kind], data, data->groups); | |
| return PTR_ERR_OR_ZERO(hwmon_dev); | |
| } | |
| #ifdef CONFIG_PM | |
| static int nct6683_suspend(struct device *dev) | |
| { | |
| struct nct6683_data *data = nct6683_update_device(dev); | |
| mutex_lock(&data->update_lock); | |
| data->hwm_cfg = nct6683_read(data, NCT6683_HWM_CFG); | |
| mutex_unlock(&data->update_lock); | |
| return 0; | |
| } | |
| static int nct6683_resume(struct device *dev) | |
| { | |
| struct nct6683_data *data = dev_get_drvdata(dev); | |
| mutex_lock(&data->update_lock); | |
| nct6683_write(data, NCT6683_HWM_CFG, data->hwm_cfg); | |
| /* Force re-reading all values */ | |
| data->valid = false; | |
| mutex_unlock(&data->update_lock); | |
| return 0; | |
| } | |
| static const struct dev_pm_ops nct6683_dev_pm_ops = { | |
| .suspend = nct6683_suspend, | |
| .resume = nct6683_resume, | |
| .freeze = nct6683_suspend, | |
| .restore = nct6683_resume, | |
| }; | |
| #define NCT6683_DEV_PM_OPS (&nct6683_dev_pm_ops) | |
| #else | |
| #define NCT6683_DEV_PM_OPS NULL | |
| #endif /* CONFIG_PM */ | |
| static struct platform_driver nct6683_driver = { | |
| .driver = { | |
| .name = DRVNAME, | |
| .pm = NCT6683_DEV_PM_OPS, | |
| }, | |
| .probe = nct6683_probe, | |
| }; | |
| static int __init nct6683_find(int sioaddr, struct nct6683_sio_data *sio_data) | |
| { | |
| int addr; | |
| u16 val; | |
| int err; | |
| err = superio_enter(sioaddr); | |
| if (err) | |
| return err; | |
| val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | |
| | superio_inb(sioaddr, SIO_REG_DEVID + 1); | |
| switch (val & SIO_ID_MASK) { | |
| case SIO_NCT6683_ID: | |
| sio_data->kind = nct6683; | |
| break; | |
| default: | |
| if (val != 0xffff) | |
| pr_debug("unsupported chip ID: 0x%04x\n", val); | |
| goto fail; | |
| } | |
| /* We have a known chip, find the HWM I/O address */ | |
| superio_select(sioaddr, NCT6683_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("EC base I/O port unconfigured\n"); | |
| goto fail; | |
| } | |
| /* Activate logical device if needed */ | |
| val = superio_inb(sioaddr, SIO_REG_ENABLE); | |
| if (!(val & 0x01)) { | |
| pr_err("EC is disabled\n"); | |
| goto fail; | |
| } | |
| superio_exit(sioaddr); | |
| pr_info("Found %s or compatible chip at %#x:%#x\n", | |
| nct6683_chip_names[sio_data->kind], sioaddr, addr); | |
| sio_data->sioreg = sioaddr; | |
| return addr; | |
| fail: | |
| superio_exit(sioaddr); | |
| return -ENODEV; | |
| } | |
| /* | |
| * 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 nct6683 driver. But since we use platform_device_alloc(), we | |
| * must keep track of the device | |
| */ | |
| static struct platform_device *pdev[2]; | |
| static int __init sensors_nct6683_init(void) | |
| { | |
| struct nct6683_sio_data sio_data; | |
| int sioaddr[2] = { 0x2e, 0x4e }; | |
| struct resource res; | |
| bool found = false; | |
| int address; | |
| int i, err; | |
| err = platform_driver_register(&nct6683_driver); | |
| if (err) | |
| return err; | |
| /* | |
| * 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 | |
| * nct6683 hardware monitor, and call probe() | |
| */ | |
| for (i = 0; i < ARRAY_SIZE(pdev); i++) { | |
| address = nct6683_find(sioaddr[i], &sio_data); | |
| if (address <= 0) | |
| continue; | |
| found = true; | |
| pdev[i] = platform_device_alloc(DRVNAME, address); | |
| if (!pdev[i]) { | |
| err = -ENOMEM; | |
| goto exit_device_unregister; | |
| } | |
| err = platform_device_add_data(pdev[i], &sio_data, | |
| sizeof(struct nct6683_sio_data)); | |
| if (err) | |
| 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) { | |
| platform_device_put(pdev[i]); | |
| pdev[i] = NULL; | |
| continue; | |
| } | |
| err = platform_device_add_resources(pdev[i], &res, 1); | |
| if (err) | |
| goto exit_device_put; | |
| /* platform_device_add calls probe() */ | |
| err = platform_device_add(pdev[i]); | |
| if (err) | |
| goto exit_device_put; | |
| } | |
| if (!found) { | |
| err = -ENODEV; | |
| goto exit_unregister; | |
| } | |
| return 0; | |
| exit_device_put: | |
| platform_device_put(pdev[i]); | |
| exit_device_unregister: | |
| while (--i >= 0) { | |
| if (pdev[i]) | |
| platform_device_unregister(pdev[i]); | |
| } | |
| exit_unregister: | |
| platform_driver_unregister(&nct6683_driver); | |
| return err; | |
| } | |
| static void __exit sensors_nct6683_exit(void) | |
| { | |
| int i; | |
| for (i = 0; i < ARRAY_SIZE(pdev); i++) { | |
| if (pdev[i]) | |
| platform_device_unregister(pdev[i]); | |
| } | |
| platform_driver_unregister(&nct6683_driver); | |
| } | |
| MODULE_AUTHOR("Guenter Roeck <linux@roeck-us.net>"); | |
| MODULE_DESCRIPTION("NCT6683D driver"); | |
| MODULE_LICENSE("GPL"); | |
| module_init(sensors_nct6683_init); | |
| module_exit(sensors_nct6683_exit); |