| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * nct6683 - Driver for the hardware monitoring functionality of |
| * Nuvoton NCT6683D/NCT6686D/NCT6687D eSIO |
| * |
| * Copyright (C) 2013 Guenter Roeck <linux@roeck-us.net> |
| * |
| * Derived from nct6775 driver |
| * Copyright (C) 2012, 2013 Guenter Roeck <linux@roeck-us.net> |
| * |
| * Supports the following chips: |
| * |
| * Chip #vin #fan #pwm #temp chip ID |
| * nct6683d 21(1) 16 8 32(1) 0xc730 |
| * nct6686d 21(1) 16 8 32(1) 0xd440 |
| * nct6687d 21(1) 16 8 32(1) 0xd590 |
| * |
| * 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, nct6686, nct6687 }; |
| |
| 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", |
| "nct6686", |
| "nct6687", |
| }; |
| |
| static const char * const nct6683_chip_names[] = { |
| "NCT6683D", |
| "NCT6686D", |
| "NCT6687D", |
| }; |
| |
| #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_NCT6686_ID 0xd440 |
| #define SIO_NCT6687_ID 0xd590 |
| #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_CUSTOMER_ID_MSI 0x201 |
| #define NCT6683_CUSTOMER_ID_MSI2 0x200 |
| #define NCT6683_CUSTOMER_ID_MSI3 0x207 |
| #define NCT6683_CUSTOMER_ID_ASROCK 0xe2c |
| #define NCT6683_CUSTOMER_ID_ASROCK2 0xe1b |
| #define NCT6683_CUSTOMER_ID_ASROCK3 0x1631 |
| |
| #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, 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_kcalloc(dev, repeat * count + 1, sizeof(*attrs), |
| GFP_KERNEL); |
| if (attrs == NULL) |
| return ERR_PTR(-ENOMEM); |
| |
| su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), |
| 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; |
| while (*t) { |
| 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 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 = kobj_to_dev(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 = kobj_to_dev(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 = kobj_to_dev(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 = kobj_to_dev(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 |
| beep_enable_show(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 |
| beep_enable_store(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 |
| intrusion0_alarm_show(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 |
| intrusion0_alarm_store(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_RW(intrusion0_alarm); |
| static DEVICE_ATTR_RW(beep_enable); |
| |
| 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; |
| case NCT6683_CUSTOMER_ID_MSI: |
| break; |
| case NCT6683_CUSTOMER_ID_MSI2: |
| break; |
| case NCT6683_CUSTOMER_ID_MSI3: |
| break; |
| case NCT6683_CUSTOMER_ID_ASROCK: |
| break; |
| case NCT6683_CUSTOMER_ID_ASROCK2: |
| break; |
| case NCT6683_CUSTOMER_ID_ASROCK3: |
| break; |
| default: |
| if (!force) |
| return -ENODEV; |
| dev_warn(dev, "Enabling support for unknown customer ID 0x%04x\n", data->customer_id); |
| break; |
| } |
| |
| 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; |
| case SIO_NCT6686_ID: |
| sio_data->kind = nct6686; |
| break; |
| case SIO_NCT6687_ID: |
| sio_data->kind = nct6687; |
| 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_warn("Forcibly enabling EC access. Data may be unusable.\n"); |
| superio_outb(sioaddr, SIO_REG_ENABLE, val | 0x01); |
| } |
| |
| 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); |