From 032c1623c9de51705d0a1c577ffdae746967e88d Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Fri, 18 Mar 2022 16:30:11 -0700 Subject: hwmon: (jc42) add HWMON_C_TZ_REGISTER Add a thermal zone interface to the devices added under jc42 driver. This way, thermal zones described in device tree can make use of the of nodes of these devices. Cc: Guenter Roeck (maintainer:JC42.4 TEMPERATURE SENSOR DRIVER) Cc: Jean Delvare (maintainer:HARDWARE MONITORING) Cc: linux-hwmon@vger.kernel.org (open list:JC42.4 TEMPERATURE SENSOR DRIVER) Cc: linux-kernel@vger.kernel.org (open list) Signed-off-by: Eduardo Valentin Signed-off-by: Eduardo Valentin Link: https://lore.kernel.org/r/20220318233011.13980-1-eduval@amazon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/jc42.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index cb347a6bd8d9..f40df2f29d41 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -443,6 +443,8 @@ static int jc42_detect(struct i2c_client *client, struct i2c_board_info *info) } static const struct hwmon_channel_info *jc42_info[] = { + HWMON_CHANNEL_INFO(chip, + HWMON_C_REGISTER_TZ | HWMON_C_UPDATE_INTERVAL), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_MIN | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_MAX_HYST | -- cgit v1.2.3 From ab9ac6df22527b22fcceb6a16f5035ee778c9688 Mon Sep 17 00:00:00 2001 From: Wei Shuyu Date: Sat, 26 Mar 2022 18:24:05 +0800 Subject: hwmon: (asus-ec-sensors) Add T_Sensor for ASUS WS X570-ACE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit WS X570-ACE has a T_Sensor header on board according to manual[1]. I'm using a 10kΩ B=3435K thermsistor attached to the header of WS X570-ACE. EC byte at 0x3d matches readings from BIOS sensor page and environment temperature. [1]https://www.asus.com/Motherboards-Components/Motherboards/All-series/Pro-WS-X570-ACE/HelpDesk_Manual/ Signed-off-by: Wei Shuyu Link: https://lore.kernel.org/r/E1nY43Q-000rAm-9a@dogben.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index b5cf0136360c..3ad8eadea68f 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -178,7 +178,8 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII DARK HERO", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | -- cgit v1.2.3 From c0c45238fcf44b05c86f2f7d1dda136df7a83ff9 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 13 Feb 2022 20:48:53 +0100 Subject: hwmon: (peci) Use devm_delayed_work_autocancel() to simplify code Use devm_delayed_work_autocancel() instead of hand writing it. This is less verbose and saves a few lines of code. devm_delayed_work_autocancel() uses devm_add_action() instead of devm_add_action_or_reset(). This is fine, because if the underlying memory allocation fails, no work has been scheduled yet. So there is nothing to undo. Signed-off-by: Christophe JAILLET Reviewed-by: Iwona Winiarska Link: https://lore.kernel.org/r/fd277a708ede3882d7df6831f02d2e3c0cb813b8.1644781718.git.christophe.jaillet@wanadoo.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/dimmtemp.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index c8222354c005..96b9919db357 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -378,13 +379,6 @@ static void create_dimm_temp_info_delayed(struct work_struct *work) dev_err(priv->dev, "Failed to populate DIMM temp info\n"); } -static void remove_delayed_work(void *_priv) -{ - struct peci_dimmtemp *priv = _priv; - - cancel_delayed_work_sync(&priv->detect_work); -} - static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxiliary_device_id *id) { struct device *dev = &adev->dev; @@ -415,9 +409,8 @@ static int peci_dimmtemp_probe(struct auxiliary_device *adev, const struct auxil "Unexpected PECI revision %#x, some features may be unavailable\n", peci_dev->info.peci_revision); - INIT_DELAYED_WORK(&priv->detect_work, create_dimm_temp_info_delayed); - - ret = devm_add_action_or_reset(priv->dev, remove_delayed_work, priv); + ret = devm_delayed_work_autocancel(priv->dev, &priv->detect_work, + create_dimm_temp_info_delayed); if (ret) return ret; -- cgit v1.2.3 From 752b927951eaa6297bffc12efe603df10496566e Mon Sep 17 00:00:00 2001 From: Aleksa Savic Date: Mon, 4 Apr 2022 15:42:11 +0200 Subject: hwmon: (aquacomputer_d5next) Add support for Aquacomputer Octo Extend aquacomputer_d5next driver to expose hardware temperature sensors and fans of the Aquacomputer Octo fan controller, which communicates through a proprietary USB HID protocol. Four temperature sensors and eight PWM controllable fans are available. Additionally, serial number, firmware version and power-on count are exposed through debugfs. This driver has been tested on x86_64. Signed-off-by: Aleksa Savic Link: https://lore.kernel.org/r/20220404134212.9690-1-savicaleksa83@gmail.com [groeck: Add missing "select CRC16"] Signed-off-by: Guenter Roeck --- Documentation/hwmon/aquacomputer_d5next.rst | 4 + drivers/hwmon/Kconfig | 8 +- drivers/hwmon/aquacomputer_d5next.c | 433 ++++++++++++++++++++++++---- 3 files changed, 393 insertions(+), 52 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst index 3373e27b707d..e69f718caf5b 100644 --- a/Documentation/hwmon/aquacomputer_d5next.rst +++ b/Documentation/hwmon/aquacomputer_d5next.rst @@ -7,6 +7,7 @@ Supported devices: * Aquacomputer D5 Next watercooling pump * Aquacomputer Farbwerk 360 RGB controller +* Aquacomputer Octo fan controller Author: Aleksa Savic @@ -28,6 +29,9 @@ seems to require sending it a complete configuration. That includes addressable RGB LEDs, for which there is no standard sysfs interface. Thus, that task is better suited for userspace tools. +The Octo exposes four temperature sensors and eight PWM controllable fans, along +with their speed (in RPM), power, voltage and current. + The Farbwerk 360 exposes four temperature sensors. Depending on the device, not all sysfs and debugfs entries will be available. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f2b038fa3b84..d35d74ef6497 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -256,11 +256,13 @@ config SENSORS_AHT10 will be called aht10. config SENSORS_AQUACOMPUTER_D5NEXT - tristate "Aquacomputer D5 Next watercooling pump" + tristate "Aquacomputer D5 Next, Octo and Farbwerk 360" depends on USB_HID + select CRC16 help - If you say yes here you get support for the Aquacomputer D5 Next - watercooling pump sensors. + If you say yes here you get support for sensors and fans of + the Aquacomputer D5 Next watercooling pump, Octo fan + controller and Farbwerk 360 RGB controller, where available. This driver can also be built as a module. If so, the module will be called aquacomputer_d5next. diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 525809cf7c95..a464473bc981 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360) + * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360, Octo) * * Aquacomputer devices send HID reports (with ID 0x01) every second to report * sensor values. @@ -8,23 +8,27 @@ * Copyright 2021 Aleksa Savic */ +#include #include #include #include #include #include +#include #include #include #define USB_VENDOR_ID_AQUACOMPUTER 0x0c70 #define USB_PRODUCT_ID_D5NEXT 0xf00e #define USB_PRODUCT_ID_FARBWERK360 0xf010 +#define USB_PRODUCT_ID_OCTO 0xf011 -enum kinds { d5next, farbwerk360 }; +enum kinds { d5next, farbwerk360, octo }; static const char *const aqc_device_names[] = { [d5next] = "d5next", - [farbwerk360] = "farbwerk360" + [farbwerk360] = "farbwerk360", + [octo] = "octo" }; #define DRIVER_NAME "aquacomputer_d5next" @@ -35,6 +39,18 @@ static const char *const aqc_device_names[] = { #define SERIAL_SECOND_PART 5 #define FIRMWARE_VERSION 13 +#define CTRL_REPORT_ID 0x03 + +/* The HID report that the official software always sends + * after writing values, currently same for all devices + */ +#define SECONDARY_CTRL_REPORT_ID 0x02 +#define SECONDARY_CTRL_REPORT_SIZE 0x0B + +static u8 secondary_ctrl_report[] = { + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x34, 0xC6 +}; + /* Register offsets for the D5 Next pump */ #define D5NEXT_POWER_CYCLES 24 @@ -55,12 +71,38 @@ static const char *const aqc_device_names[] = { /* Register offsets for the Farbwerk 360 RGB controller */ #define FARBWERK360_NUM_SENSORS 4 -#define FARBWERK360_SENSOR_START 0x32 +#define FARBWERK360_SENSOR_START 0x32 #define FARBWERK360_SENSOR_SIZE 0x02 #define FARBWERK360_SENSOR_DISCONNECTED 0x7FFF +/* Register offsets for the Octo fan controller */ +#define OCTO_POWER_CYCLES 0x18 +#define OCTO_NUM_FANS 8 +#define OCTO_FAN_PERCENT_OFFSET 0x00 +#define OCTO_FAN_VOLTAGE_OFFSET 0x02 +#define OCTO_FAN_CURRENT_OFFSET 0x04 +#define OCTO_FAN_POWER_OFFSET 0x06 +#define OCTO_FAN_SPEED_OFFSET 0x08 + +static u8 octo_sensor_fan_offsets[] = { 0x7D, 0x8A, 0x97, 0xA4, 0xB1, 0xBE, 0xCB, 0xD8 }; + +#define OCTO_NUM_SENSORS 4 +#define OCTO_SENSOR_START 0x3D +#define OCTO_SENSOR_SIZE 0x02 +#define OCTO_SENSOR_DISCONNECTED 0x7FFF + +#define OCTO_CTRL_REPORT_SIZE 0x65F +#define OCTO_CTRL_REPORT_CHECKSUM_OFFSET 0x65D +#define OCTO_CTRL_REPORT_CHECKSUM_START 0x01 +#define OCTO_CTRL_REPORT_CHECKSUM_LENGTH 0x65C + +/* Fan speed registers in Octo control report (from 0-100%) */ +static u16 octo_ctrl_fan_offsets[] = { 0x5B, 0xB0, 0x105, 0x15A, 0x1AF, 0x204, 0x259, 0x2AE }; + /* Labels for D5 Next */ -#define L_D5NEXT_COOLANT_TEMP "Coolant temp" +static const char *const label_d5next_temp[] = { + "Coolant temp" +}; static const char *const label_d5next_speeds[] = { "Pump speed", @@ -83,7 +125,7 @@ static const char *const label_d5next_current[] = { "Fan current" }; -/* Labels for Farbwerk 360 temperature sensors */ +/* Labels for Farbwerk 360 and Octo temperature sensors */ static const char *const label_temp_sensors[] = { "Sensor 1", "Sensor 2", @@ -91,32 +133,182 @@ static const char *const label_temp_sensors[] = { "Sensor 4" }; +/* Labels for Octo */ +static const char *const label_fan_speed[] = { + "Fan 1 speed", + "Fan 2 speed", + "Fan 3 speed", + "Fan 4 speed", + "Fan 5 speed", + "Fan 6 speed", + "Fan 7 speed", + "Fan 8 speed" +}; + +static const char *const label_fan_power[] = { + "Fan 1 power", + "Fan 2 power", + "Fan 3 power", + "Fan 4 power", + "Fan 5 power", + "Fan 6 power", + "Fan 7 power", + "Fan 8 power" +}; + +static const char *const label_fan_voltage[] = { + "Fan 1 voltage", + "Fan 2 voltage", + "Fan 3 voltage", + "Fan 4 voltage", + "Fan 5 voltage", + "Fan 6 voltage", + "Fan 7 voltage", + "Fan 8 voltage" +}; + +static const char *const label_fan_current[] = { + "Fan 1 current", + "Fan 2 current", + "Fan 3 current", + "Fan 4 current", + "Fan 5 current", + "Fan 6 current", + "Fan 7 current", + "Fan 8 current" +}; + struct aqc_data { struct hid_device *hdev; struct device *hwmon_dev; struct dentry *debugfs; + struct mutex mutex; /* Used for locking access when reading and writing PWM values */ enum kinds kind; const char *name; + int buffer_size; + u8 *buffer; + int checksum_start; + int checksum_length; + int checksum_offset; + /* General info, same across all devices */ u32 serial_number[2]; u16 firmware_version; - /* D5 Next specific - how many times the device was powered on */ + /* How many times the device was powered on */ u32 power_cycles; /* Sensor values */ s32 temp_input[4]; - u16 speed_input[2]; - u32 power_input[2]; - u16 voltage_input[3]; - u16 current_input[2]; + u16 speed_input[8]; + u32 power_input[8]; + u16 voltage_input[8]; + u16 current_input[8]; + + /* Label values */ + const char *const *temp_label; + const char *const *speed_label; + const char *const *power_label; + const char *const *voltage_label; + const char *const *current_label; unsigned long updated; }; -static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, - int channel) +/* Converts from centi-percent */ +static int aqc_percent_to_pwm(u16 val) +{ + return DIV_ROUND_CLOSEST(val * 255, 100 * 100); +} + +/* Converts to centi-percent */ +static int aqc_pwm_to_percent(long val) +{ + if (val < 0 || val > 255) + return -EINVAL; + + return DIV_ROUND_CLOSEST(val * 100 * 100, 255); +} + +/* Expects the mutex to be locked */ +static int aqc_get_ctrl_data(struct aqc_data *priv) +{ + int ret; + + memset(priv->buffer, 0x00, priv->buffer_size); + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) + ret = -ENODATA; + + return ret; +} + +/* Expects the mutex to be locked */ +static int aqc_send_ctrl_data(struct aqc_data *priv) +{ + int ret; + u16 checksum; + + /* Init and xorout value for CRC-16/USB is 0xffff */ + checksum = crc16(0xffff, priv->buffer + priv->checksum_start, priv->checksum_length); + checksum ^= 0xffff; + + /* Place the new checksum at the end of the report */ + put_unaligned_be16(checksum, priv->buffer + priv->checksum_offset); + + /* Send the patched up report back to the device */ + ret = hid_hw_raw_request(priv->hdev, CTRL_REPORT_ID, priv->buffer, priv->buffer_size, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret < 0) + return ret; + + /* The official software sends this report after every change, so do it here as well */ + ret = hid_hw_raw_request(priv->hdev, SECONDARY_CTRL_REPORT_ID, secondary_ctrl_report, + SECONDARY_CTRL_REPORT_SIZE, HID_FEATURE_REPORT, + HID_REQ_SET_REPORT); + return ret; +} + +/* Refreshes the control buffer and returns value at offset */ +static int aqc_get_ctrl_val(struct aqc_data *priv, int offset) +{ + int ret; + + mutex_lock(&priv->mutex); + + ret = aqc_get_ctrl_data(priv); + if (ret < 0) + goto unlock_and_return; + + ret = get_unaligned_be16(priv->buffer + offset); + +unlock_and_return: + mutex_unlock(&priv->mutex); + return ret; +} + +static int aqc_set_ctrl_val(struct aqc_data *priv, int offset, long val) +{ + int ret; + + mutex_lock(&priv->mutex); + + ret = aqc_get_ctrl_data(priv); + if (ret < 0) + goto unlock_and_return; + + put_unaligned_be16((u16)val, priv->buffer + offset); + + ret = aqc_send_ctrl_data(priv); + +unlock_and_return: + mutex_unlock(&priv->mutex); + return ret; +} + +static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { const struct aqc_data *priv = data; @@ -128,17 +320,47 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 return 0444; break; case farbwerk360: + case octo: return 0444; default: break; } break; + case hwmon_pwm: + switch (priv->kind) { + case octo: + switch (attr) { + case hwmon_pwm_input: + return 0644; + default: + break; + } + break; + default: + break; + } + break; case hwmon_fan: case hwmon_power: - case hwmon_in: case hwmon_curr: switch (priv->kind) { case d5next: + if (channel < 2) + return 0444; + break; + case octo: + return 0444; + default: + break; + } + break; + case hwmon_in: + switch (priv->kind) { + case d5next: + if (channel < 3) + return 0444; + break; + case octo: return 0444; default: break; @@ -154,6 +376,7 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *val) { + int ret; struct aqc_data *priv = dev_get_drvdata(dev); if (time_after(jiffies, priv->updated + STATUS_UPDATE_INTERVAL)) @@ -172,6 +395,19 @@ static int aqc_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, case hwmon_power: *val = priv->power_input[channel]; break; + case hwmon_pwm: + switch (priv->kind) { + case octo: + ret = aqc_get_ctrl_val(priv, octo_ctrl_fan_offsets[channel]); + if (ret < 0) + return ret; + + *val = aqc_percent_to_pwm(ret); + break; + default: + break; + } + break; case hwmon_in: *val = priv->voltage_input[channel]; break; @@ -192,48 +428,51 @@ static int aqc_read_string(struct device *dev, enum hwmon_sensor_types type, u32 switch (type) { case hwmon_temp: - switch (priv->kind) { - case d5next: - *str = L_D5NEXT_COOLANT_TEMP; - break; - case farbwerk360: - *str = label_temp_sensors[channel]; - break; - default: - break; - } + *str = priv->temp_label[channel]; break; case hwmon_fan: - switch (priv->kind) { - case d5next: - *str = label_d5next_speeds[channel]; - break; - default: - break; - } + *str = priv->speed_label[channel]; break; case hwmon_power: - switch (priv->kind) { - case d5next: - *str = label_d5next_power[channel]; - break; - default: - break; - } + *str = priv->power_label[channel]; break; case hwmon_in: - switch (priv->kind) { - case d5next: - *str = label_d5next_voltages[channel]; - break; - default: - break; - } + *str = priv->voltage_label[channel]; break; case hwmon_curr: - switch (priv->kind) { - case d5next: - *str = label_d5next_current[channel]; + *str = priv->current_label[channel]; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int aqc_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, + long val) +{ + int ret, pwm_value; + struct aqc_data *priv = dev_get_drvdata(dev); + + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + switch (priv->kind) { + case octo: + pwm_value = aqc_pwm_to_percent(val); + if (pwm_value < 0) + return pwm_value; + + ret = aqc_set_ctrl_val(priv, octo_ctrl_fan_offsets[channel], + pwm_value); + if (ret < 0) + return ret; + break; + default: + break; + } break; default: break; @@ -250,6 +489,7 @@ static const struct hwmon_ops aqc_hwmon_ops = { .is_visible = aqc_is_visible, .read = aqc_read, .read_string = aqc_read_string, + .write = aqc_write }; static const struct hwmon_channel_info *aqc_info[] = { @@ -259,16 +499,48 @@ static const struct hwmon_channel_info *aqc_info[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, + HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL, HWMON_F_INPUT | HWMON_F_LABEL), HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, + HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL, HWMON_P_INPUT | HWMON_P_LABEL), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT, + HWMON_PWM_INPUT), HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL, HWMON_I_INPUT | HWMON_I_LABEL), HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, + HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL, HWMON_C_INPUT | HWMON_C_LABEL), NULL @@ -326,6 +598,35 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->temp_input[i] = sensor_value * 10; } break; + case octo: + priv->power_cycles = get_unaligned_be32(data + OCTO_POWER_CYCLES); + + /* Fan speed and related readings */ + for (i = 0; i < OCTO_NUM_FANS; i++) { + priv->speed_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_SPEED_OFFSET); + priv->power_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_POWER_OFFSET) * 10000; + priv->voltage_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_VOLTAGE_OFFSET) * 10; + priv->current_input[i] = + get_unaligned_be16(data + octo_sensor_fan_offsets[i] + + OCTO_FAN_CURRENT_OFFSET); + } + + /* Temperature sensor readings */ + for (i = 0; i < OCTO_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + OCTO_SENSOR_START + + i * OCTO_SENSOR_SIZE); + if (sensor_value == OCTO_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; default: break; } @@ -378,8 +679,14 @@ static void aqc_debugfs_init(struct aqc_data *priv) debugfs_create_file("serial_number", 0444, priv->debugfs, priv, &serial_number_fops); debugfs_create_file("firmware_version", 0444, priv->debugfs, priv, &firmware_version_fops); - if (priv->kind == d5next) + switch (priv->kind) { + case d5next: + case octo: debugfs_create_file("power_cycles", 0444, priv->debugfs, priv, &power_cycles_fops); + break; + default: + break; + } } #else @@ -419,9 +726,30 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) switch (hdev->product) { case USB_PRODUCT_ID_D5NEXT: priv->kind = d5next; + + priv->temp_label = label_d5next_temp; + priv->speed_label = label_d5next_speeds; + priv->power_label = label_d5next_power; + priv->voltage_label = label_d5next_voltages; + priv->current_label = label_d5next_current; break; case USB_PRODUCT_ID_FARBWERK360: priv->kind = farbwerk360; + + priv->temp_label = label_temp_sensors; + break; + case USB_PRODUCT_ID_OCTO: + priv->kind = octo; + priv->buffer_size = OCTO_CTRL_REPORT_SIZE; + priv->checksum_start = OCTO_CTRL_REPORT_CHECKSUM_START; + priv->checksum_length = OCTO_CTRL_REPORT_CHECKSUM_LENGTH; + priv->checksum_offset = OCTO_CTRL_REPORT_CHECKSUM_OFFSET; + + priv->temp_label = label_temp_sensors; + priv->speed_label = label_fan_speed; + priv->power_label = label_fan_power; + priv->voltage_label = label_fan_voltage; + priv->current_label = label_fan_current; break; default: break; @@ -429,6 +757,12 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->name = aqc_device_names[priv->kind]; + priv->buffer = devm_kzalloc(&hdev->dev, priv->buffer_size, GFP_KERNEL); + if (!priv->buffer) + return -ENOMEM; + + mutex_init(&priv->mutex); + priv->hwmon_dev = hwmon_device_register_with_info(&hdev->dev, priv->name, priv, &aqc_chip_info, NULL); @@ -462,6 +796,7 @@ static void aqc_remove(struct hid_device *hdev) static const struct hid_device_id aqc_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) }, { } }; -- cgit v1.2.3 From 1ad6c3b7ef132e1d8c5d606008069724625c8daf Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 5 Apr 2022 11:24:51 +0200 Subject: hwmon: introduce hwmon_sanitize_name() More and more drivers will check for bad characters in the hwmon name and all are using the same code snippet. Consolidate that code by adding a new hwmon_sanitize_name() function. Signed-off-by: Michael Walle Reviewed-by: Tom Rix Link: https://lore.kernel.org/r/20220405092452.4033674-2-michael@walle.cc Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 16 ++++++++++ drivers/hwmon/hwmon.c | 53 ++++++++++++++++++++++++++++++++ include/linux/hwmon.h | 3 ++ 3 files changed, 72 insertions(+) (limited to 'drivers') diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index c41eb6108103..e2975d5caf34 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -50,6 +50,10 @@ register/unregister functions:: void devm_hwmon_device_unregister(struct device *dev); + char *hwmon_sanitize_name(const char *name); + + char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + hwmon_device_register_with_groups registers a hardware monitoring device. The first parameter of this function is a pointer to the parent device. The name parameter is a pointer to the hwmon device name. The registration @@ -95,6 +99,18 @@ All supported hwmon device registration functions only accept valid device names. Device names including invalid characters (whitespace, '*', or '-') will be rejected. The 'name' parameter is mandatory. +If the driver doesn't use a static device name (for example it uses +dev_name()), and therefore cannot make sure the name only contains valid +characters, hwmon_sanitize_name can be used. This convenience function +will duplicate the string and replace any invalid characters with an +underscore. It will allocate memory for the new string and it is the +responsibility of the caller to release the memory when the device is +removed. + +devm_hwmon_sanitize_name is the resource managed version of +hwmon_sanitize_name; the memory will be freed automatically on device +removal. + Using devm_hwmon_device_register_with_info() -------------------------------------------- diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 989e2c8496dd..5915ccfdb7d9 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -1057,6 +1057,59 @@ void devm_hwmon_device_unregister(struct device *dev) } EXPORT_SYMBOL_GPL(devm_hwmon_device_unregister); +static char *__hwmon_sanitize_name(struct device *dev, const char *old_name) +{ + char *name, *p; + + if (dev) + name = devm_kstrdup(dev, old_name, GFP_KERNEL); + else + name = kstrdup(old_name, GFP_KERNEL); + if (!name) + return ERR_PTR(-ENOMEM); + + for (p = name; *p; p++) + if (hwmon_is_bad_char(*p)) + *p = '_'; + + return name; +} + +/** + * hwmon_sanitize_name - Replaces invalid characters in a hwmon name + * @name: NUL-terminated name + * + * Allocates a new string where any invalid characters will be replaced + * by an underscore. It is the responsibility of the caller to release + * the memory. + * + * Returns newly allocated name, or ERR_PTR on error. + */ +char *hwmon_sanitize_name(const char *name) +{ + return __hwmon_sanitize_name(NULL, name); +} +EXPORT_SYMBOL_GPL(hwmon_sanitize_name); + +/** + * devm_hwmon_sanitize_name - resource managed hwmon_sanitize_name() + * @dev: device to allocate memory for + * @name: NUL-terminated name + * + * Allocates a new string where any invalid characters will be replaced + * by an underscore. + * + * Returns newly allocated name, or ERR_PTR on error. + */ +char *devm_hwmon_sanitize_name(struct device *dev, const char *name) +{ + if (!dev) + return ERR_PTR(-EINVAL); + + return __hwmon_sanitize_name(dev, name); +} +EXPORT_SYMBOL_GPL(devm_hwmon_sanitize_name); + static void __init hwmon_pci_quirks(void) { #if defined CONFIG_X86 && defined CONFIG_PCI diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index eba380b76d15..4efaf06fd2b8 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -461,6 +461,9 @@ void devm_hwmon_device_unregister(struct device *dev); int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel); +char *hwmon_sanitize_name(const char *name); +char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + /** * hwmon_is_bad_char - Is the char invalid in a hwmon name * @ch: the char to be considered -- cgit v1.2.3 From 340b3b6aa47ae13e1d46ecb5e03ec2c260603f63 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Tue, 5 Apr 2022 11:24:52 +0200 Subject: hwmon: (intel-m10-bmc-hwmon) use devm_hwmon_sanitize_name() Instead of open-coding the bad characters replacement in the hwmon name, use the new devm_hwmon_sanitize_name(). Signed-off-by: Michael Walle Acked-by: Xu Yilun Reviewed-by: Tom Rix Link: https://lore.kernel.org/r/20220405092452.4033674-3-michael@walle.cc Signed-off-by: Guenter Roeck --- drivers/hwmon/intel-m10-bmc-hwmon.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/intel-m10-bmc-hwmon.c b/drivers/hwmon/intel-m10-bmc-hwmon.c index 7a08e4c44a4b..6e82f7200d1c 100644 --- a/drivers/hwmon/intel-m10-bmc-hwmon.c +++ b/drivers/hwmon/intel-m10-bmc-hwmon.c @@ -515,7 +515,6 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev) struct intel_m10bmc *m10bmc = dev_get_drvdata(pdev->dev.parent); struct device *hwmon_dev, *dev = &pdev->dev; struct m10bmc_hwmon *hw; - int i; hw = devm_kzalloc(dev, sizeof(*hw), GFP_KERNEL); if (!hw) @@ -528,13 +527,9 @@ static int m10bmc_hwmon_probe(struct platform_device *pdev) hw->chip.info = hw->bdata->hinfo; hw->chip.ops = &m10bmc_hwmon_ops; - hw->hw_name = devm_kstrdup(dev, id->name, GFP_KERNEL); - if (!hw->hw_name) - return -ENOMEM; - - for (i = 0; hw->hw_name[i]; i++) - if (hwmon_is_bad_char(hw->hw_name[i])) - hw->hw_name[i] = '_'; + hw->hw_name = devm_hwmon_sanitize_name(dev, id->name); + if (IS_ERR(hw->hw_name)) + return PTR_ERR(hw->hw_name); hwmon_dev = devm_hwmon_device_register_with_info(dev, hw->hw_name, hw, &hw->chip, NULL); -- cgit v1.2.3 From d7cc063ff09b86daaeca691b254cea9526a9a5f7 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Fri, 22 Apr 2022 13:17:37 +0200 Subject: hwmon: (asus-ec-sensors) add ProArt X570 Creator WIFI board Basing on information and testing provided by users [1] add support for another board, ASUS ProArt X570 Creator WiFi. [1] https://github.com/zeule/asus-ec-sensors/issues/17 Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220422111737.1352610-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 5 +++++ 2 files changed, 6 insertions(+) (limited to 'drivers') diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index e7e8f1640f45..b3469851ab9a 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -6,6 +6,7 @@ Kernel driver asus_ec_sensors Supported boards: * PRIME X570-PRO, * Pro WS X570-ACE, + * ProArt X570-CREATOR WIFI * ROG CROSSHAIR VIII DARK HERO, * ROG CROSSHAIR VIII HERO (WI-FI) * ROG CROSSHAIR VIII FORMULA, diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 3ad8eadea68f..e3d794fb0534 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -176,6 +176,11 @@ static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), + DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, + "ProArt X570-CREATOR WIFI", + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | -- cgit v1.2.3 From e0d3f7cb26063898a14fee2fea7d38fc8ceeb89a Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Sun, 10 Apr 2022 18:39:35 +0200 Subject: hwmon: (dell-smm) Add cooling device support Until now, only the temperature sensors where exported thru the thermal subsystem. Export the fans as "dell-smm-fan[1-3]" too to make them available as cooling devices. Also update Documentation and fix a minor issue with the alphabetic ordering of the includes. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220410163935.7840-1-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/hwmon/dell-smm-hwmon.rst | 7 +++ drivers/hwmon/Kconfig | 1 + drivers/hwmon/dell-smm-hwmon.c | 97 ++++++++++++++++++++++++++++++++-- 3 files changed, 101 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/dell-smm-hwmon.rst b/Documentation/hwmon/dell-smm-hwmon.rst index d3323a96665d..41839b7de2c1 100644 --- a/Documentation/hwmon/dell-smm-hwmon.rst +++ b/Documentation/hwmon/dell-smm-hwmon.rst @@ -86,6 +86,13 @@ probe the BIOS on your machine and discover the appropriate codes. Again, when you find new codes, we'd be happy to have your patches! +``thermal`` interface +--------------------------- + +The driver also exports the fans as thermal cooling devices with +``type`` set to ``dell-smm-fan[1-3]``. This allows for easy fan control +using one of the thermal governors. + Module parameters ----------------- diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d35d74ef6497..7fc3dacc4710 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -500,6 +500,7 @@ config SENSORS_DS1621 config SENSORS_DELL_SMM tristate "Dell laptop SMM BIOS hwmon driver" depends on X86 + imply THERMAL help This hwmon driver adds support for reporting temperature of different sensors and controls the fans on Dell laptops via System Management diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 84cb1ede7bc0..30b6f0c28093 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -21,14 +21,17 @@ #include #include #include +#include #include #include #include #include #include #include -#include +#include #include +#include +#include #include #include @@ -80,6 +83,11 @@ struct dell_smm_data { int *fan_nominal_speed[DELL_SMM_NO_FANS]; }; +struct dell_smm_cooling_data { + u8 fan_num; + struct dell_smm_data *data; +}; + MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); MODULE_AUTHOR("Pali Rohár "); MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); @@ -638,9 +646,50 @@ static void __init i8k_init_procfs(struct device *dev) #endif -/* - * Hwmon interface - */ +static int dell_smm_get_max_state(struct thermal_cooling_device *dev, unsigned long *state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + + *state = cdata->data->i8k_fan_max; + + return 0; +} + +static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned long *state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + int ret; + + ret = i8k_get_fan_status(cdata->data, cdata->fan_num); + if (ret < 0) + return ret; + + *state = ret; + + return 0; +} + +static int dell_smm_set_cur_state(struct thermal_cooling_device *dev, unsigned long state) +{ + struct dell_smm_cooling_data *cdata = dev->devdata; + struct dell_smm_data *data = cdata->data; + int ret; + + if (state > data->i8k_fan_max) + return -EINVAL; + + mutex_lock(&data->i8k_mutex); + ret = i8k_set_fan(data, cdata->fan_num, (int)state); + mutex_unlock(&data->i8k_mutex); + + return ret; +} + +static const struct thermal_cooling_device_ops dell_smm_cooling_ops = { + .get_max_state = dell_smm_get_max_state, + .get_cur_state = dell_smm_get_cur_state, + .set_cur_state = dell_smm_set_cur_state, +}; static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, int channel) @@ -941,6 +990,37 @@ static const struct hwmon_chip_info dell_smm_chip_info = { .info = dell_smm_info, }; +static int __init dell_smm_init_cdev(struct device *dev, u8 fan_num) +{ + struct dell_smm_data *data = dev_get_drvdata(dev); + struct thermal_cooling_device *cdev; + struct dell_smm_cooling_data *cdata; + int ret = 0; + char *name; + + name = kasprintf(GFP_KERNEL, "dell-smm-fan%u", fan_num + 1); + if (!name) + return -ENOMEM; + + cdata = devm_kmalloc(dev, sizeof(*cdata), GFP_KERNEL); + if (cdata) { + cdata->fan_num = fan_num; + cdata->data = data; + cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata, + &dell_smm_cooling_ops); + if (IS_ERR(cdev)) { + devm_kfree(dev, cdata); + ret = PTR_ERR(cdev); + } + } else { + ret = -ENOMEM; + } + + kfree(name); + + return ret; +} + static int __init dell_smm_init_hwmon(struct device *dev) { struct dell_smm_data *data = dev_get_drvdata(dev); @@ -967,6 +1047,15 @@ static int __init dell_smm_init_hwmon(struct device *dev) continue; data->fan[i] = true; + + /* the cooling device is not critical, ignore failures */ + if (IS_REACHABLE(CONFIG_THERMAL)) { + err = dell_smm_init_cdev(dev, i); + if (err < 0) + dev_warn(dev, "Failed to register cooling device for fan %u\n", + i + 1); + } + data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1, sizeof(*data->fan_nominal_speed[i]), GFP_KERNEL); -- cgit v1.2.3 From bd79021072ac841e73ec5300a4dbfbdeff431582 Mon Sep 17 00:00:00 2001 From: Camel Guo Date: Thu, 14 Apr 2022 09:58:23 +0200 Subject: hwmon: (tmp401) Add support of three advanced features tmp401 driver supports TMP401, TMP411 and TMP43X temperature sensors. According to their datasheet: - all of them support extended temperature range feature; - TMP411 and TPM43X support n-factor correction feature; - TMP43X support beta compensation feature. In order to support setting them during bootup, this commit reads ti,extended-range-enable, ti,n-factor and ti,beta-compensation and set the corresponding registers during probing. Signed-off-by: Camel Guo Link: https://lore.kernel.org/r/20220414075824.2634839-3-camel.guo@axis.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 52c9e7d3f2ae..63e53c8989a3 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -41,6 +41,8 @@ enum chips { tmp401, tmp411, tmp431, tmp432, tmp435 }; #define TMP401_STATUS 0x02 #define TMP401_CONFIG 0x03 #define TMP401_CONVERSION_RATE 0x04 +#define TMP4XX_N_FACTOR_REG 0x18 +#define TMP43X_BETA_RANGE 0x25 #define TMP401_TEMP_CRIT_HYST 0x21 #define TMP401_MANUFACTURER_ID_REG 0xFE #define TMP401_DEVICE_ID_REG 0xFF @@ -543,6 +545,8 @@ static int tmp401_init_client(struct tmp401_data *data) struct regmap *regmap = data->regmap; u32 config, config_orig; int ret; + u32 val = 0; + s32 nfactor = 0; /* Set conversion rate to 2 Hz */ ret = regmap_write(regmap, TMP401_CONVERSION_RATE, 5); @@ -557,10 +561,48 @@ static int tmp401_init_client(struct tmp401_data *data) config_orig = config; config &= ~TMP401_CONFIG_SHUTDOWN; + if (of_property_read_bool(data->client->dev.of_node, "ti,extended-range-enable")) { + /* Enable measurement over extended temperature range */ + config |= TMP401_CONFIG_RANGE; + } + data->extended_range = !!(config & TMP401_CONFIG_RANGE); - if (config != config_orig) + if (config != config_orig) { ret = regmap_write(regmap, TMP401_CONFIG, config); + if (ret < 0) + return ret; + } + + ret = of_property_read_u32(data->client->dev.of_node, "ti,n-factor", &nfactor); + if (!ret) { + if (data->kind == tmp401) { + dev_err(&data->client->dev, "ti,tmp401 does not support n-factor correction\n"); + return -EINVAL; + } + if (nfactor < -128 || nfactor > 127) { + dev_err(&data->client->dev, "n-factor is invalid (%d)\n", nfactor); + return -EINVAL; + } + ret = regmap_write(regmap, TMP4XX_N_FACTOR_REG, (unsigned int)nfactor); + if (ret < 0) + return ret; + } + + ret = of_property_read_u32(data->client->dev.of_node, "ti,beta-compensation", &val); + if (!ret) { + if (data->kind == tmp401 || data->kind == tmp411) { + dev_err(&data->client->dev, "ti,tmp401 or ti,tmp411 does not support beta compensation\n"); + return -EINVAL; + } + if (val > 15) { + dev_err(&data->client->dev, "beta-compensation is invalid (%u)\n", val); + return -EINVAL; + } + ret = regmap_write(regmap, TMP43X_BETA_RANGE, val); + if (ret < 0) + return ret; + } return ret; } -- cgit v1.2.3 From c7250b5d553cae39bdfcac95ef5bdac6f6a5b022 Mon Sep 17 00:00:00 2001 From: Oleksandr Shamray Date: Mon, 18 Apr 2022 15:12:59 +0300 Subject: hwmon: (jc42) Add support for S-34TS04A S-34TS04A is a JC42 compatible 2-wire serial EEPROM with temperature sensor from Seiko Instruments/ABLIC. Signed-off-by: Oleksandr Shamray Reviewed-by: Vadim Pasternak Signed-off-by: Guenter Roeck --- drivers/hwmon/jc42.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index f40df2f29d41..07f7f8b5b73d 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -63,6 +63,7 @@ static const unsigned short normal_i2c[] = { #define STM_MANID 0x104a /* ST Microelectronics */ #define GT_MANID 0x1c68 /* Giantec */ #define GT_MANID2 0x132d /* Giantec, 2nd mfg ID */ +#define SI_MANID 0x1c85 /* Seiko Instruments */ /* SMBUS register */ #define SMBUS_STMOUT BIT(7) /* SMBus time-out, active low */ @@ -156,6 +157,10 @@ static const unsigned short normal_i2c[] = { #define STTS3000_DEVID 0x0200 #define STTS3000_DEVID_MASK 0xffff +/* Seiko Instruments */ +#define S34TS04A_DEVID 0x2221 +#define S34TS04A_DEVID_MASK 0xffff + static u16 jc42_hysteresis[] = { 0, 1500, 3000, 6000 }; struct jc42_chips { @@ -186,6 +191,7 @@ static struct jc42_chips jc42_chips[] = { { ONS_MANID, CAT34TS04_DEVID, CAT34TS04_DEVID_MASK }, { ONS_MANID, N34TS04_DEVID, N34TS04_DEVID_MASK }, { NXP_MANID, SE98_DEVID, SE98_DEVID_MASK }, + { SI_MANID, S34TS04A_DEVID, S34TS04A_DEVID_MASK }, { STM_MANID, STTS424_DEVID, STTS424_DEVID_MASK }, { STM_MANID, STTS424E_DEVID, STTS424E_DEVID_MASK }, { STM_MANID, STTS2002_DEVID, STTS2002_DEVID_MASK }, -- cgit v1.2.3 From 229b159c505a7ee5da86f952db1e644b213ccb61 Mon Sep 17 00:00:00 2001 From: Jack Doan Date: Sun, 24 Apr 2022 00:14:22 -0500 Subject: hwmon: (aquacomputer_d5next) Add support for Aquacomputer Farbwerk Extend aquacomputer_d5next driver to expose hardware temperature sensors of the Aquacomputer Farbwerk RGB controller, which communicates through a proprietary USB HID protocol. Four temperature sensors are available. Additionally, serial number and firmware version are exposed through debugfs. Also, add Jack Doan to MAINTAINERS for this driver. Signed-off-by: Jack Doan Signed-off-by: Aleksa Savic Link: https://lore.kernel.org/r/YmTcrq8Gzel0zYYD@jackdesk Signed-off-by: Guenter Roeck --- Documentation/hwmon/aquacomputer_d5next.rst | 3 ++- MAINTAINERS | 1 + drivers/hwmon/Kconfig | 5 ++-- drivers/hwmon/aquacomputer_d5next.c | 37 +++++++++++++++++++++++++---- 4 files changed, 38 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/aquacomputer_d5next.rst b/Documentation/hwmon/aquacomputer_d5next.rst index e69f718caf5b..717e28226cde 100644 --- a/Documentation/hwmon/aquacomputer_d5next.rst +++ b/Documentation/hwmon/aquacomputer_d5next.rst @@ -6,6 +6,7 @@ Kernel driver aquacomputer-d5next Supported devices: * Aquacomputer D5 Next watercooling pump +* Aquacomputer Farbwerk RGB controller * Aquacomputer Farbwerk 360 RGB controller * Aquacomputer Octo fan controller @@ -32,7 +33,7 @@ better suited for userspace tools. The Octo exposes four temperature sensors and eight PWM controllable fans, along with their speed (in RPM), power, voltage and current. -The Farbwerk 360 exposes four temperature sensors. Depending on the device, +The Farbwerk and Farbwerk 360 expose four temperature sensors. Depending on the device, not all sysfs and debugfs entries will be available. Usage notes diff --git a/MAINTAINERS b/MAINTAINERS index 6d2c2dbbe01b..7ead62833c09 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1447,6 +1447,7 @@ F: drivers/media/i2c/aptina-pll.* AQUACOMPUTER D5 NEXT PUMP SENSOR DRIVER M: Aleksa Savic +M: Jack Doan L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/aquacomputer_d5next.rst diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 7fc3dacc4710..a001224bfb26 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -256,13 +256,14 @@ config SENSORS_AHT10 will be called aht10. config SENSORS_AQUACOMPUTER_D5NEXT - tristate "Aquacomputer D5 Next, Octo and Farbwerk 360" + tristate "Aquacomputer D5 Next, Octo, Farbwerk, and Farbwerk 360" depends on USB_HID select CRC16 help If you say yes here you get support for sensors and fans of the Aquacomputer D5 Next watercooling pump, Octo fan - controller and Farbwerk 360 RGB controller, where available. + controller, Farbwerk and Farbwerk 360 RGB controllers, where + available. This driver can also be built as a module. If so, the module will be called aquacomputer_d5next. diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index a464473bc981..7d2e7279abfb 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -1,11 +1,12 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk 360, Octo) + * hwmon driver for Aquacomputer devices (D5 Next, Farbwerk, Farbwerk 360, Octo) * * Aquacomputer devices send HID reports (with ID 0x01) every second to report * sensor values. * * Copyright 2021 Aleksa Savic + * Copyright 2022 Jack Doan */ #include @@ -19,14 +20,16 @@ #include #define USB_VENDOR_ID_AQUACOMPUTER 0x0c70 +#define USB_PRODUCT_ID_FARBWERK 0xf00a #define USB_PRODUCT_ID_D5NEXT 0xf00e #define USB_PRODUCT_ID_FARBWERK360 0xf010 #define USB_PRODUCT_ID_OCTO 0xf011 -enum kinds { d5next, farbwerk360, octo }; +enum kinds { d5next, farbwerk, farbwerk360, octo }; static const char *const aqc_device_names[] = { [d5next] = "d5next", + [farbwerk] = "farbwerk", [farbwerk360] = "farbwerk360", [octo] = "octo" }; @@ -69,6 +72,12 @@ static u8 secondary_ctrl_report[] = { #define D5NEXT_PUMP_CURRENT 112 #define D5NEXT_FAN_CURRENT 99 +/* Register offsets for the Farbwerk RGB controller */ +#define FARBWERK_NUM_SENSORS 4 +#define FARBWERK_SENSOR_START 0x2f +#define FARBWERK_SENSOR_SIZE 0x02 +#define FARBWERK_SENSOR_DISCONNECTED 0x7FFF + /* Register offsets for the Farbwerk 360 RGB controller */ #define FARBWERK360_NUM_SENSORS 4 #define FARBWERK360_SENSOR_START 0x32 @@ -125,7 +134,7 @@ static const char *const label_d5next_current[] = { "Fan current" }; -/* Labels for Farbwerk 360 and Octo temperature sensors */ +/* Labels for Farbwerk, Farbwerk 360 and Octo temperature sensors */ static const char *const label_temp_sensors[] = { "Sensor 1", "Sensor 2", @@ -319,6 +328,7 @@ static umode_t aqc_is_visible(const void *data, enum hwmon_sensor_types type, u3 if (channel == 0) return 0444; break; + case farbwerk: case farbwerk360: case octo: return 0444; @@ -551,8 +561,7 @@ static const struct hwmon_chip_info aqc_chip_info = { .info = aqc_info, }; -static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, - int size) +static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { int i, sensor_value; struct aqc_data *priv; @@ -587,6 +596,17 @@ static int aqc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 priv->current_input[0] = get_unaligned_be16(data + D5NEXT_PUMP_CURRENT); priv->current_input[1] = get_unaligned_be16(data + D5NEXT_FAN_CURRENT); break; + case farbwerk: + /* Temperature sensor readings */ + for (i = 0; i < FARBWERK_NUM_SENSORS; i++) { + sensor_value = get_unaligned_be16(data + FARBWERK_SENSOR_START + + i * FARBWERK_SENSOR_SIZE); + if (sensor_value == FARBWERK_SENSOR_DISCONNECTED) + priv->temp_input[i] = -ENODATA; + else + priv->temp_input[i] = sensor_value * 10; + } + break; case farbwerk360: /* Temperature sensor readings */ for (i = 0; i < FARBWERK360_NUM_SENSORS; i++) { @@ -733,6 +753,11 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->voltage_label = label_d5next_voltages; priv->current_label = label_d5next_current; break; + case USB_PRODUCT_ID_FARBWERK: + priv->kind = farbwerk; + + priv->temp_label = label_temp_sensors; + break; case USB_PRODUCT_ID_FARBWERK360: priv->kind = farbwerk360; @@ -795,6 +820,7 @@ static void aqc_remove(struct hid_device *hdev) static const struct hid_device_id aqc_table[] = { { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_D5NEXT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_FARBWERK360) }, { HID_USB_DEVICE(USB_VENDOR_ID_AQUACOMPUTER, USB_PRODUCT_ID_OCTO) }, { } @@ -826,4 +852,5 @@ module_exit(aqc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Aleksa Savic "); +MODULE_AUTHOR("Jack Doan "); MODULE_DESCRIPTION("Hwmon driver for Aquacomputer devices"); -- cgit v1.2.3 From e5c498ccc90c87155d1f6980cac050d48b8a84b4 Mon Sep 17 00:00:00 2001 From: Atif Ofluoglu Date: Fri, 15 Apr 2022 16:34:29 +0300 Subject: hwmon: (pmbus/max16601) Add MAX16602 support Adding another MAX16602 chip support to MAX16601 driver Tested with MAX16602 works as expected. Signed-off-by: Atif Ofluoglu Signed-off-by: Guenter Roeck --- Documentation/hwmon/max16601.rst | 8 ++++++++ drivers/hwmon/pmbus/Kconfig | 4 ++-- drivers/hwmon/pmbus/max16601.c | 13 ++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/max16601.rst b/Documentation/hwmon/max16601.rst index 92c0a7d7808c..6a4eef8efbaf 100644 --- a/Documentation/hwmon/max16601.rst +++ b/Documentation/hwmon/max16601.rst @@ -21,6 +21,14 @@ Supported chips: Datasheet: Not published + * Maxim MAX16602 + + Prefix: 'max16602' + + Addresses scanned: - + + Datasheet: https://datasheets.maximintegrated.com/en/ds/MAX16602.pdf + Author: Guenter Roeck diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index a2ea1d5a8765..53683d2d0fb2 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -228,10 +228,10 @@ config SENSORS_MAX16064 be called max16064. config SENSORS_MAX16601 - tristate "Maxim MAX16508, MAX16601" + tristate "Maxim MAX16508, MAX16601, MAX16602" help If you say yes here you get hardware monitoring support for Maxim - MAX16508 and MAX16601. + MAX16508, MAX16601 and MAX16602. This driver can also be built as a module. If so, the module will be called max16601. diff --git a/drivers/hwmon/pmbus/max16601.c b/drivers/hwmon/pmbus/max16601.c index 5a226a564776..b628405e6586 100644 --- a/drivers/hwmon/pmbus/max16601.c +++ b/drivers/hwmon/pmbus/max16601.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Hardware monitoring driver for Maxim MAX16508 and MAX16601. + * Hardware monitoring driver for Maxim MAX16508, MAX16601 and MAX16602. * * Implementation notes: * @@ -31,7 +31,7 @@ #include "pmbus.h" -enum chips { max16508, max16601 }; +enum chips { max16508, max16601, max16602 }; #define REG_DEFAULT_NUM_POP 0xc4 #define REG_SETPT_DVID 0xd1 @@ -202,7 +202,7 @@ static int max16601_identify(struct i2c_client *client, else info->vrm_version[0] = vr12; - if (data->id != max16601) + if (data->id != max16601 && data->id != max16602) return 0; reg = i2c_smbus_read_byte_data(client, REG_DEFAULT_NUM_POP); @@ -264,6 +264,7 @@ static void max16601_remove(void *_data) static const struct i2c_device_id max16601_id[] = { {"max16508", max16508}, {"max16601", max16601}, + {"max16602", max16602}, {} }; MODULE_DEVICE_TABLE(i2c, max16601_id); @@ -280,13 +281,15 @@ static int max16601_get_id(struct i2c_client *client) return -ENODEV; /* - * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" - * or "MAX16500y.xx". + * PMBUS_IC_DEVICE_ID is expected to return "MAX16601y.xx" or + * MAX16602y.xx or "MAX16500y.xx".cdxxcccccccccc */ if (!strncmp(buf, "MAX16500", 8)) { id = max16508; } else if (!strncmp(buf, "MAX16601", 8)) { id = max16601; + } else if (!strncmp(buf, "MAX16602", 8)) { + id = max16602; } else { buf[ret] = '\0'; dev_err(dev, "Unsupported chip '%s'\n", buf); -- cgit v1.2.3 From 8c099cd381aa36373593d4efc92493c1345c73da Mon Sep 17 00:00:00 2001 From: Camel Guo Date: Mon, 25 Apr 2022 12:00:19 +0200 Subject: hwmon: (tmp401) Fix incorrect return value of tmp401_init_client When ti,n-factor, ti,beta-compentation are not defined in devicetree, of_property_read_u32|s32 returns -EINVAL. In this case, tmp401_init_client should return 0 instead of simply pass ret to its caller. Signed-off-by: Camel Guo Link: https://lore.kernel.org/r/20220425100019.562781-1-camel.guo@axis.com Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp401.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/tmp401.c b/drivers/hwmon/tmp401.c index 63e53c8989a3..cc0a1c219b1f 100644 --- a/drivers/hwmon/tmp401.c +++ b/drivers/hwmon/tmp401.c @@ -604,7 +604,7 @@ static int tmp401_init_client(struct tmp401_data *data) return ret; } - return ret; + return 0; } static int tmp401_detect(struct i2c_client *client, -- cgit v1.2.3 From c851b715d38de0c262a63de16ad954ed39b47aca Mon Sep 17 00:00:00 2001 From: Peter Rosin Date: Mon, 25 Apr 2022 22:35:50 +0200 Subject: hwmon: (lm75) Add Atmel AT30TS74 support Atmel (now Microchip) AT30TS74 is an LM75 compatible sensor. Add it. Signed-off-by: Peter Rosin Link: https://lore.kernel.org/r/9494dfbc-f506-3e94-501d-6760c487c93d@axentia.se Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/lm75.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index a001224bfb26..3db1b01a6666 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1252,6 +1252,7 @@ config SENSORS_LM75 temperature sensor chip, with models including: - Analog Devices ADT75 + - Atmel (now Microchip) AT30TS74 - Dallas Semiconductor DS75, DS1775 and DS7505 - Global Mixed-mode Technology (GMT) G751 - Maxim MAX6625 and MAX6626 diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index afdbb63237b9..66dc826f7962 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -26,6 +26,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ adt75, + at30ts74, ds1775, ds75, ds7505, @@ -128,6 +129,14 @@ static const struct lm75_params device_params[] = { .default_resolution = 12, .default_sample_time = MSEC_PER_SEC / 10, }, + [at30ts74] = { + .set_mask = 3 << 5, /* 12-bit mode*/ + .default_resolution = 12, + .default_sample_time = 200, + .num_sample_times = 4, + .sample_times = (unsigned int []){ 25, 50, 100, 200 }, + .resolutions = (u8 []) {9, 10, 11, 12 }, + }, [ds1775] = { .clr_mask = 3 << 5, .set_mask = 2 << 5, /* 11-bit mode */ @@ -645,6 +654,7 @@ static int lm75_probe(struct i2c_client *client) static const struct i2c_device_id lm75_ids[] = { { "adt75", adt75, }, + { "at30ts74", at30ts74, }, { "ds1775", ds1775, }, { "ds75", ds75, }, { "ds7505", ds7505, }, @@ -680,6 +690,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = { .compatible = "adi,adt75", .data = (void *)adt75 }, + { + .compatible = "atmel,at30ts74", + .data = (void *)at30ts74 + }, { .compatible = "dallas,ds1775", .data = (void *)ds1775 -- cgit v1.2.3 From 4fc1a51c8572179abb767f56cbfc433e00e310c1 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 26 Apr 2022 23:31:52 +0200 Subject: hwmon: (dell-smm) Avoid unnecessary SMM calls during init When the driver tries to detect the fan multiplier during module initialisation, it issues one SMM call for each fan. Those SMM calls are however redundant and also try to query fans which may not be present. Fix that by detecting the fan multiplier during hwmon initialisation when no extra SMM calls are needed. Also dont assume the last nominal speed entry to be the biggest and instead check all entries. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220426213154.724708-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 30b6f0c28093..202ee884de9e 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -50,7 +50,7 @@ #define I8K_SMM_GET_DELL_SIG2 0xffa3 #define I8K_FAN_MULT 30 -#define I8K_FAN_MAX_RPM 30000 +#define I8K_FAN_RPM_THRESHOLD 1000 #define I8K_MAX_TEMP 127 #define I8K_FN_NONE 0x00 @@ -326,7 +326,7 @@ static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 if (data->disallow_fan_support) return -EINVAL; - return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; + return i8k_smm(®s) ? : (regs.eax & 0xffff); } /* @@ -776,6 +776,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a long *val) { struct dell_smm_data *data = dev_get_drvdata(dev); + int mult = data->i8k_fan_mult; int ret; switch (type) { @@ -804,11 +805,11 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a return 0; case hwmon_fan_min: - *val = data->fan_nominal_speed[channel][0]; + *val = data->fan_nominal_speed[channel][0] * mult; return 0; case hwmon_fan_max: - *val = data->fan_nominal_speed[channel][data->i8k_fan_max]; + *val = data->fan_nominal_speed[channel][data->i8k_fan_max] * mult; return 0; case hwmon_fan_target: @@ -819,7 +820,7 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a if (ret > data->i8k_fan_max) ret = data->i8k_fan_max; - *val = data->fan_nominal_speed[channel][ret]; + *val = data->fan_nominal_speed[channel][ret] * mult; return 0; default: @@ -1071,6 +1072,13 @@ static int __init dell_smm_init_hwmon(struct device *dev) break; } data->fan_nominal_speed[i][state] = err; + /* + * Autodetect fan multiplier based on nominal rpm if multiplier + * was not specified as module param or in DMI. If fan reports + * rpm value too high then set multiplier to 1. + */ + if (!fan_mult && err > I8K_FAN_RPM_THRESHOLD) + data->i8k_fan_mult = 1; } } @@ -1359,7 +1367,6 @@ static int __init dell_smm_probe(struct platform_device *pdev) struct dell_smm_data *data; const struct dmi_system_id *id, *fan_control; int ret; - u8 fan; data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); if (!data) @@ -1414,24 +1421,8 @@ static int __init dell_smm_probe(struct platform_device *pdev) dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); } - if (!fan_mult) { - /* - * Autodetect fan multiplier based on nominal rpm - * If fan reports rpm value too high then set multiplier to 1 - */ - for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) { - ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max); - if (ret < 0) - continue; - - if (ret > I8K_FAN_MAX_RPM) - data->i8k_fan_mult = 1; - break; - } - } else { - /* Fan multiplier was specified in module param or in dmi */ + if (fan_mult) data->i8k_fan_mult = fan_mult; - } ret = dell_smm_init_hwmon(&pdev->dev); if (ret) -- cgit v1.2.3 From f44aa665ee9647a204066b287c8fc8c604440425 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 26 Apr 2022 23:31:53 +0200 Subject: hwmon: (dell-smm) Cleanup init code The default values for i8k_fan_mult and i8k_fan_max should be assigend only if the values specified as module params or in DMI are invalid/missing. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220426213154.724708-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 202ee884de9e..f13902414615 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1373,8 +1373,6 @@ static int __init dell_smm_probe(struct platform_device *pdev) return -ENOMEM; mutex_init(&data->i8k_mutex); - data->i8k_fan_mult = I8K_FAN_MULT; - data->i8k_fan_max = I8K_FAN_HIGH; platform_set_drvdata(pdev, data); if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { @@ -1409,7 +1407,9 @@ static int __init dell_smm_probe(struct platform_device *pdev) fan_max = conf->fan_max; } - data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ + /* All options must not be 0 */ + data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT; + data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); fan_control = dmi_first_match(i8k_whitelist_fan_control); @@ -1421,9 +1421,6 @@ static int __init dell_smm_probe(struct platform_device *pdev) dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); } - if (fan_mult) - data->i8k_fan_mult = fan_mult; - ret = dell_smm_init_hwmon(&pdev->dev); if (ret) return ret; -- cgit v1.2.3 From 981c5f3c744bbe2e6420a8a834d52c7ed2a75b7f Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 26 Apr 2022 23:31:54 +0200 Subject: hwmon: (dell-smm) Warn if SMM call took a very long time to execute If a particular SMM call takes a very long time to execute, the user might experience audio problems. Print a warning if a particular SMM call took over 0.250 seconds to execute, so the user can check whether or not possible audio problems are caused by this driver. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20220426213154.724708-4-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index f13902414615..071aa6f4e109 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -49,6 +49,9 @@ #define I8K_SMM_GET_DELL_SIG1 0xfea3 #define I8K_SMM_GET_DELL_SIG2 0xffa3 +/* in usecs */ +#define DELL_SMM_MAX_DURATION 250000 + #define I8K_FAN_MULT 30 #define I8K_FAN_RPM_THRESHOLD 1000 #define I8K_MAX_TEMP 127 @@ -239,6 +242,9 @@ static int i8k_smm_func(void *par) pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lld usecs)\n", eax, ebx, (rc ? 0xffff : regs->eax & 0xffff), duration); + if (duration > DELL_SMM_MAX_DURATION) + pr_warn_once("SMM call took %lld usecs!\n", duration); + return rc; } -- cgit v1.2.3 From 4ef2774511dc6e552228bd6c3fefa2e7d41f38b7 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Tue, 26 Apr 2022 18:01:49 -0700 Subject: hwmon: (nct6775) Convert register access to regmap API This replaces the nct6775_data->{read,write}_value function pointers with a regmap. The major difference is that the regmap access functions may fail, and hence require checking at each call site. While the existing WMI register-access code had potential failure paths, they were masked by the fact that the read_value() function returned the register value directly, and hence squashed errors undetectably by simply returning zero, and while the write_value() functions were capable of reporting errors, all callers ignored them. This improves the robustness of the existing code, and also prepares the driver for an i2c version to be added soon, for which register accesses are much more likely to actually fail. The conversion of the register-access call sites is largely mechanical (reading a register now returns the value via an out-param pointer, and returned errors must be checked for and propagated to callers), though the nct6775_write_fan_div() function is refactored slightly to avoid duplicating nearly identical (and now lengthier) code in each switch case. Signed-off-by: Zev Weiss Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220427010154.29749-3-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 977 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 662 insertions(+), 315 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 2b91f7e05126..be99a1890ccd 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -55,6 +55,7 @@ #include #include #include +#include #include #include "lm75.h" @@ -1345,9 +1346,7 @@ struct nct6775_data { u8 fandiv2; u8 sio_reg_enable; - /* nct6775_*() callbacks */ - u16 (*read_value)(struct nct6775_data *data, u16 reg); - int (*write_value)(struct nct6775_data *data, u16 reg, u16 value); + struct regmap *regmap; }; struct sensor_device_template { @@ -1546,30 +1545,44 @@ static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg) data->bank = bank; } -static u16 nct6775_wmi_read_value(struct nct6775_data *data, u16 reg) +static int nct6775_wmi_reg_read(void *ctx, unsigned int reg, unsigned int *val) { - int res, err, word_sized = is_word_sized(data, reg); + struct nct6775_data *data = ctx; + int err, word_sized = is_word_sized(data, reg); u8 tmp = 0; + u16 res; nct6775_wmi_set_bank(data, reg); err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); if (err) - return 0; + return err; res = tmp; if (word_sized) { err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp); if (err) - return 0; + return err; res = (res << 8) + tmp; } - return res; + *val = res; + return 0; +} + +static inline int nct6775_read_value(struct nct6775_data *data, u16 reg, u16 *value) +{ + unsigned int tmp; + int ret = regmap_read(data->regmap, reg, &tmp); + + if (!ret) + *value = tmp; + return ret; } -static int nct6775_wmi_write_value(struct nct6775_data *data, u16 reg, u16 value) +static int nct6775_wmi_reg_write(void *ctx, unsigned int reg, unsigned int value) { + struct nct6775_data *data = ctx; int res, word_sized = is_word_sized(data, reg); nct6775_wmi_set_bank(data, reg); @@ -1587,6 +1600,11 @@ static int nct6775_wmi_write_value(struct nct6775_data *data, u16 reg, u16 value return res; } +static inline int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +{ + return regmap_write(data->regmap, reg, value); +} + /* * On older chips, only registers 0x50-0x5f are banked. * On more recent chips, all registers are banked. @@ -1604,23 +1622,25 @@ static inline void nct6775_set_bank(struct nct6775_data *data, u16 reg) } } -static u16 nct6775_read_value(struct nct6775_data *data, u16 reg) +static int nct6775_reg_read(void *ctx, unsigned int reg, unsigned int *val) { - int res, word_sized = is_word_sized(data, reg); + struct nct6775_data *data = ctx; + int word_sized = is_word_sized(data, reg); nct6775_set_bank(data, reg); outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); - res = inb_p(data->addr + DATA_REG_OFFSET); + *val = 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); + *val = (*val << 8) + inb_p(data->addr + DATA_REG_OFFSET); } - return res; + return 0; } -static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +static int nct6775_reg_write(void *ctx, unsigned int reg, unsigned int value) { + struct nct6775_data *data = ctx; int word_sized = is_word_sized(data, reg); nct6775_set_bank(data, reg); @@ -1635,83 +1655,85 @@ static int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) } /* We left-align 8-bit temperature values to make the code simpler */ -static u16 nct6775_read_temp(struct nct6775_data *data, u16 reg) +static int nct6775_read_temp(struct nct6775_data *data, u16 reg, u16 *val) { - u16 res; + int err; + + err = nct6775_read_value(data, reg, val); + if (err) + return err; - res = data->read_value(data, reg); if (!is_word_sized(data, reg)) - res <<= 8; + *val <<= 8; - return res; + return 0; } static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) { if (!is_word_sized(data, reg)) value >>= 8; - return data->write_value(data, reg, value); + return nct6775_write_value(data, reg, value); } /* This function assumes that the caller holds data->update_lock */ -static void nct6775_write_fan_div(struct nct6775_data *data, int nr) +static int nct6775_write_fan_div(struct nct6775_data *data, int nr) { - u8 reg; + u16 reg; + int err; + u16 fandiv_reg = nr < 2 ? NCT6775_REG_FANDIV1 : NCT6775_REG_FANDIV2; + unsigned int oddshift = (nr & 1) * 4; /* masks shift by four if nr is odd */ - switch (nr) { - case 0: - reg = (data->read_value(data, NCT6775_REG_FANDIV1) & 0x70) - | (data->fan_div[0] & 0x7); - data->write_value(data, NCT6775_REG_FANDIV1, reg); - break; - case 1: - reg = (data->read_value(data, NCT6775_REG_FANDIV1) & 0x7) - | ((data->fan_div[1] << 4) & 0x70); - data->write_value(data, NCT6775_REG_FANDIV1, reg); - break; - case 2: - reg = (data->read_value(data, NCT6775_REG_FANDIV2) & 0x70) - | (data->fan_div[2] & 0x7); - data->write_value(data, NCT6775_REG_FANDIV2, reg); - break; - case 3: - reg = (data->read_value(data, NCT6775_REG_FANDIV2) & 0x7) - | ((data->fan_div[3] << 4) & 0x70); - data->write_value(data, NCT6775_REG_FANDIV2, reg); - break; - } + err = nct6775_read_value(data, fandiv_reg, ®); + if (err) + return err; + reg &= 0x70 >> oddshift; + reg |= data->fan_div[nr] & (0x7 << oddshift); + return nct6775_write_value(data, fandiv_reg, reg); } -static void nct6775_write_fan_div_common(struct nct6775_data *data, int nr) +static int nct6775_write_fan_div_common(struct nct6775_data *data, int nr) { if (data->kind == nct6775) - nct6775_write_fan_div(data, nr); + return nct6775_write_fan_div(data, nr); + return 0; } -static void nct6775_update_fan_div(struct nct6775_data *data) +static int nct6775_update_fan_div(struct nct6775_data *data) { - u8 i; + int err; + u16 i; - i = data->read_value(data, NCT6775_REG_FANDIV1); + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &i); + if (err) + return err; data->fan_div[0] = i & 0x7; data->fan_div[1] = (i & 0x70) >> 4; - i = data->read_value(data, NCT6775_REG_FANDIV2); + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &i); + if (err) + return err; data->fan_div[2] = i & 0x7; if (data->has_fan & BIT(3)) data->fan_div[3] = (i & 0x70) >> 4; + + return 0; } -static void nct6775_update_fan_div_common(struct nct6775_data *data) +static int nct6775_update_fan_div_common(struct nct6775_data *data) { if (data->kind == nct6775) - nct6775_update_fan_div(data); + return nct6775_update_fan_div(data); + return 0; } -static void nct6775_init_fan_div(struct nct6775_data *data) +static int nct6775_init_fan_div(struct nct6775_data *data) { - int i; + int i, err; + + err = nct6775_update_fan_div_common(data); + if (err) + return err; - nct6775_update_fan_div_common(data); /* * For all fans, start with highest divider value if the divider * register is not initialized. This ensures that we get a @@ -1723,19 +1745,26 @@ static void nct6775_init_fan_div(struct nct6775_data *data) continue; if (data->fan_div[i] == 0) { data->fan_div[i] = 7; - nct6775_write_fan_div_common(data, i); + err = nct6775_write_fan_div_common(data, i); + if (err) + return err; } } + + return 0; } -static void nct6775_init_fan_common(struct device *dev, - struct nct6775_data *data) +static int nct6775_init_fan_common(struct device *dev, + struct nct6775_data *data) { - int i; - u8 reg; + int i, err; + u16 reg; - if (data->has_fan_div) - nct6775_init_fan_div(data); + if (data->has_fan_div) { + err = nct6775_init_fan_div(data); + if (err) + return err; + } /* * If fan_min is not set (0), set it to 0xff to disable it. This @@ -1743,23 +1772,30 @@ static void nct6775_init_fan_common(struct device *dev, */ for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { if (data->has_fan_min & BIT(i)) { - reg = data->read_value(data, data->REG_FAN_MIN[i]); - if (!reg) - data->write_value(data, data->REG_FAN_MIN[i], - data->has_fan_div ? 0xff - : 0xff1f); + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + return err; + if (!reg) { + err = nct6775_write_value(data, data->REG_FAN_MIN[i], + data->has_fan_div ? 0xff : 0xff1f); + if (err) + return err; + } } } + + return 0; } -static void nct6775_select_fan_div(struct device *dev, - struct nct6775_data *data, int nr, u16 reg) +static int nct6775_select_fan_div(struct device *dev, + struct nct6775_data *data, int nr, u16 reg) { + int err; u8 fan_div = data->fan_div[nr]; u16 fan_min; if (!data->has_fan_div) - return; + return 0; /* * If we failed to measure the fan speed, or the reported value is not @@ -1791,36 +1827,46 @@ static void nct6775_select_fan_div(struct device *dev, } if (fan_min != data->fan_min[nr]) { data->fan_min[nr] = fan_min; - data->write_value(data, data->REG_FAN_MIN[nr], - fan_min); + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], fan_min); + if (err) + return err; } } data->fan_div[nr] = fan_div; - nct6775_write_fan_div_common(data, nr); + err = nct6775_write_fan_div_common(data, nr); + if (err) + return err; } + + return 0; } -static void nct6775_update_pwm(struct device *dev) +static int nct6775_update_pwm(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; - int fanmodecfg, reg; + int i, j, err; + u16 fanmodecfg, reg; bool duty_is_dc; for (i = 0; i < data->pwm_num; i++) { if (!(data->has_pwm & BIT(i))) continue; - duty_is_dc = data->REG_PWM_MODE[i] && - (data->read_value(data, data->REG_PWM_MODE[i]) - & data->PWM_MODE_MASK[i]); + err = nct6775_read_value(data, data->REG_PWM_MODE[i], ®); + if (err) + return err; + duty_is_dc = data->REG_PWM_MODE[i] && (reg & data->PWM_MODE_MASK[i]); data->pwm_mode[i] = !duty_is_dc; - fanmodecfg = data->read_value(data, data->REG_FAN_MODE[i]); + err = nct6775_read_value(data, data->REG_FAN_MODE[i], &fanmodecfg); + if (err) + return err; for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { if (data->REG_PWM[j] && data->REG_PWM[j][i]) { - data->pwm[j][i] = data->read_value(data, - data->REG_PWM[j][i]); + err = nct6775_read_value(data, data->REG_PWM[j][i], ®); + if (err) + return err; + data->pwm[j][i] = reg; } } @@ -1835,17 +1881,22 @@ static void nct6775_update_pwm(struct device *dev) u8 t = fanmodecfg & 0x0f; if (data->REG_TOLERANCE_H) { - t |= (data->read_value(data, - data->REG_TOLERANCE_H[i]) & 0x70) >> 1; + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + t |= (reg & 0x70) >> 1; } data->target_speed_tolerance[i] = t; } - data->temp_tolerance[1][i] = - data->read_value(data, - data->REG_CRITICAL_TEMP_TOLERANCE[i]); + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[i], ®); + if (err) + return err; + data->temp_tolerance[1][i] = reg; - reg = data->read_value(data, data->REG_TEMP_SEL[i]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], ®); + if (err) + return err; data->pwm_temp_sel[i] = reg & 0x1f; /* If fan can stop, report floor as 0 */ if (reg & 0x80) @@ -1854,7 +1905,9 @@ static void nct6775_update_pwm(struct device *dev) if (!data->REG_WEIGHT_TEMP_SEL[i]) continue; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[i]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i], ®); + if (err) + return err; data->pwm_weight_temp_sel[i] = reg & 0x1f; /* If weight is disabled, report weight source as 0 */ if (!(reg & 0x80)) @@ -1862,29 +1915,37 @@ static void nct6775_update_pwm(struct device *dev) /* Weight temp data */ for (j = 0; j < ARRAY_SIZE(data->weight_temp); j++) { - data->weight_temp[j][i] = data->read_value(data, - data->REG_WEIGHT_TEMP[j][i]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP[j][i], ®); + if (err) + return err; + data->weight_temp[j][i] = reg; } } + + return 0; } -static void nct6775_update_pwm_limits(struct device *dev) +static int nct6775_update_pwm_limits(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; - u8 reg; - u16 reg_t; + int i, j, err; + u16 reg, reg_t; for (i = 0; i < data->pwm_num; i++) { if (!(data->has_pwm & BIT(i))) continue; for (j = 0; j < ARRAY_SIZE(data->fan_time); j++) { - data->fan_time[j][i] = - data->read_value(data, data->REG_FAN_TIME[j][i]); + err = nct6775_read_value(data, data->REG_FAN_TIME[j][i], ®); + if (err) + return err; + data->fan_time[j][i] = reg; } - reg_t = data->read_value(data, data->REG_TARGET[i]); + err = nct6775_read_value(data, data->REG_TARGET[i], ®_t); + if (err) + return err; + /* Update only in matching mode or if never updated */ if (!data->target_temp[i] || data->pwm_enable[i] == thermal_cruise) @@ -1892,29 +1953,37 @@ static void nct6775_update_pwm_limits(struct device *dev) if (!data->target_speed[i] || data->pwm_enable[i] == speed_cruise) { if (data->REG_TOLERANCE_H) { - reg_t |= (data->read_value(data, - data->REG_TOLERANCE_H[i]) & 0x0f) << 8; + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + reg_t |= (reg & 0x0f) << 8; } data->target_speed[i] = reg_t; } for (j = 0; j < data->auto_pwm_num; j++) { - data->auto_pwm[i][j] = - data->read_value(data, - NCT6775_AUTO_PWM(data, i, j)); - data->auto_temp[i][j] = - data->read_value(data, - NCT6775_AUTO_TEMP(data, i, j)); + err = nct6775_read_value(data, NCT6775_AUTO_PWM(data, i, j), ®); + if (err) + return err; + data->auto_pwm[i][j] = reg; + + err = nct6775_read_value(data, NCT6775_AUTO_TEMP(data, i, j), ®); + if (err) + return err; + data->auto_temp[i][j] = reg; } /* critical auto_pwm temperature data */ - data->auto_temp[i][data->auto_pwm_num] = - data->read_value(data, data->REG_CRITICAL_TEMP[i]); + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP[i], ®); + if (err) + return err; + data->auto_temp[i][data->auto_pwm_num] = reg; switch (data->kind) { case nct6775: - reg = data->read_value(data, - NCT6775_REG_CRITICAL_ENAB[i]); + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[i], ®); + if (err) + return err; data->auto_pwm[i][data->auto_pwm_num] = (reg & 0x02) ? 0xff : 0x00; break; @@ -1931,120 +2000,158 @@ static void nct6775_update_pwm_limits(struct device *dev) case nct6796: case nct6797: case nct6798: - reg = data->read_value(data, - data->REG_CRITICAL_PWM_ENABLE[i]); - if (reg & data->CRITICAL_PWM_ENABLE_MASK) - reg = data->read_value(data, - data->REG_CRITICAL_PWM[i]); - else + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[i], ®); + if (err) + return err; + if (reg & data->CRITICAL_PWM_ENABLE_MASK) { + err = nct6775_read_value(data, data->REG_CRITICAL_PWM[i], ®); + if (err) + return err; + } else { reg = 0xff; + } data->auto_pwm[i][data->auto_pwm_num] = reg; break; } } + + return 0; } static struct nct6775_data *nct6775_update_device(struct device *dev) { struct nct6775_data *data = dev_get_drvdata(dev); - int i, j; + int i, j, err = 0; + u16 reg; mutex_lock(&data->update_lock); if (time_after(jiffies, data->last_updated + HZ + HZ / 2) || !data->valid) { /* Fan clock dividers */ - nct6775_update_fan_div_common(data); + err = nct6775_update_fan_div_common(data); + if (err) + goto out; /* Measured voltages and limits */ for (i = 0; i < data->in_num; i++) { if (!(data->have_in & BIT(i))) continue; - data->in[i][0] = data->read_value(data, - data->REG_VIN[i]); - data->in[i][1] = data->read_value(data, - data->REG_IN_MINMAX[0][i]); - data->in[i][2] = data->read_value(data, - data->REG_IN_MINMAX[1][i]); + err = nct6775_read_value(data, data->REG_VIN[i], ®); + if (err) + goto out; + data->in[i][0] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[0][i], ®); + if (err) + goto out; + data->in[i][1] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[1][i], ®); + if (err) + goto out; + data->in[i][2] = reg; } /* Measured fan speeds and limits */ for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { - u16 reg; - if (!(data->has_fan & BIT(i))) continue; - reg = data->read_value(data, data->REG_FAN[i]); + err = nct6775_read_value(data, data->REG_FAN[i], ®); + if (err) + goto out; data->rpm[i] = data->fan_from_reg(reg, data->fan_div[i]); - if (data->has_fan_min & BIT(i)) - data->fan_min[i] = data->read_value(data, - data->REG_FAN_MIN[i]); + if (data->has_fan_min & BIT(i)) { + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + goto out; + data->fan_min[i] = reg; + } if (data->REG_FAN_PULSES[i]) { - data->fan_pulses[i] = - (data->read_value(data, - data->REG_FAN_PULSES[i]) - >> data->FAN_PULSE_SHIFT[i]) & 0x03; + err = nct6775_read_value(data, data->REG_FAN_PULSES[i], ®); + if (err) + goto out; + data->fan_pulses[i] = (reg >> data->FAN_PULSE_SHIFT[i]) & 0x03; } - nct6775_select_fan_div(dev, data, i, reg); + err = nct6775_select_fan_div(dev, data, i, reg); + if (err) + goto out; } - nct6775_update_pwm(dev); - nct6775_update_pwm_limits(dev); + err = nct6775_update_pwm(dev); + if (err) + goto out; + + err = nct6775_update_pwm_limits(dev); + if (err) + goto out; /* Measured temperatures and limits */ for (i = 0; i < NUM_TEMP; i++) { if (!(data->have_temp & BIT(i))) continue; for (j = 0; j < ARRAY_SIZE(data->reg_temp); j++) { - if (data->reg_temp[j][i]) - data->temp[j][i] = nct6775_read_temp(data, - data->reg_temp[j][i]); + if (data->reg_temp[j][i]) { + err = nct6775_read_temp(data, data->reg_temp[j][i], ®); + if (err) + goto out; + data->temp[j][i] = reg; + } } if (i >= NUM_TEMP_FIXED || !(data->have_temp_fixed & BIT(i))) continue; - data->temp_offset[i] = data->read_value(data, - data->REG_TEMP_OFFSET[i]); + err = nct6775_read_value(data, data->REG_TEMP_OFFSET[i], ®); + if (err) + goto out; + data->temp_offset[i] = reg; } for (i = 0; i < NUM_TSI_TEMP; i++) { if (!(data->have_tsi_temp & BIT(i))) continue; - data->tsi_temp[i] = data->read_value(data, data->REG_TSI_TEMP[i]); + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], ®); + if (err) + goto out; + data->tsi_temp[i] = reg; } data->alarms = 0; for (i = 0; i < NUM_REG_ALARM; i++) { - u8 alarm; + u16 alarm; if (!data->REG_ALARM[i]) continue; - alarm = data->read_value(data, data->REG_ALARM[i]); + err = nct6775_read_value(data, data->REG_ALARM[i], &alarm); + if (err) + goto out; data->alarms |= ((u64)alarm) << (i << 3); } data->beeps = 0; for (i = 0; i < NUM_REG_BEEP; i++) { - u8 beep; + u16 beep; if (!data->REG_BEEP[i]) continue; - beep = data->read_value(data, data->REG_BEEP[i]); + err = nct6775_read_value(data, data->REG_BEEP[i], &beep); + if (err) + goto out; data->beeps |= ((u64)beep) << (i << 3); } data->last_updated = jiffies; data->valid = true; } - +out: mutex_unlock(&data->update_lock); - return data; + return err ? ERR_PTR(err) : data; } /* @@ -2058,6 +2165,9 @@ show_in_reg(struct device *dev, struct device_attribute *attr, char *buf) int index = sattr->index; int nr = sattr->nr; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%ld\n", in_from_reg(data->in[nr][index], nr)); } @@ -2077,10 +2187,9 @@ store_in_reg(struct device *dev, struct device_attribute *attr, const char *buf, return err; mutex_lock(&data->update_lock); data->in[nr][index] = in_to_reg(val, nr); - data->write_value(data, data->REG_IN_MINMAX[index - 1][nr], - data->in[nr][index]); + err = nct6775_write_value(data, data->REG_IN_MINMAX[index - 1][nr], data->in[nr][index]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2088,8 +2197,12 @@ 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]; + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + nr = data->ALARM_BITS[sattr->index]; return sprintf(buf, "%u\n", (unsigned int)((data->alarms >> nr) & 0x01)); } @@ -2097,14 +2210,15 @@ show_alarm(struct device *dev, struct device_attribute *attr, char *buf) static int find_temp_source(struct nct6775_data *data, int index, int count) { int source = data->temp_src[index]; - int nr; + int nr, err; for (nr = 0; nr < count; nr++) { - int src; + u16 src; - src = data->read_value(data, - data->REG_TEMP_SOURCE[nr]) & 0x1f; - if (src == source) + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[nr], &src); + if (err) + return err; + if ((src & 0x1f) == source) return nr; } return -ENODEV; @@ -2118,6 +2232,9 @@ show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) unsigned int alarm = 0; int nr; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * For temperatures, there is no fixed mapping from registers to alarm * bits. Alarm bits are determined by the temperature source mapping. @@ -2136,7 +2253,12 @@ show_beep(struct device *dev, struct device_attribute *attr, char *buf) { struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); struct nct6775_data *data = nct6775_update_device(dev); - int nr = data->BEEP_BITS[sattr->index]; + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + nr = data->BEEP_BITS[sattr->index]; return sprintf(buf, "%u\n", (unsigned int)((data->beeps >> nr) & 0x01)); @@ -2164,10 +2286,10 @@ store_beep(struct device *dev, struct device_attribute *attr, const char *buf, data->beeps |= (1ULL << nr); else data->beeps &= ~(1ULL << nr); - data->write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2178,6 +2300,9 @@ show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) unsigned int beep = 0; int nr; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * For temperatures, there is no fixed mapping from registers to beep * enable bits. Beep enable bits are determined by the temperature @@ -2220,11 +2345,11 @@ store_temp_beep(struct device *dev, struct device_attribute *attr, data->beeps |= (1ULL << bit); else data->beeps &= ~(1ULL << bit); - data->write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_in_is_visible(struct kobject *kobj, @@ -2275,6 +2400,9 @@ show_fan(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->rpm[nr]); } @@ -2285,6 +2413,9 @@ show_fan_min(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->fan_from_reg_min(data->fan_min[nr], data->fan_div[nr])); @@ -2297,6 +2428,9 @@ show_fan_div(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); } @@ -2382,16 +2516,18 @@ write_div: nr + 1, div_from_reg(data->fan_div[nr]), div_from_reg(new_div)); data->fan_div[nr] = new_div; - nct6775_write_fan_div_common(data, nr); + err = nct6775_write_fan_div_common(data, nr); + if (err) + goto write_min; /* Give the chip time to sample a new speed value */ data->last_updated = jiffies; } write_min: - data->write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2399,8 +2535,12 @@ show_fan_pulses(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 p = data->fan_pulses[sattr->index]; + int p; + if (IS_ERR(data)) + return PTR_ERR(data); + + p = data->fan_pulses[sattr->index]; return sprintf(buf, "%d\n", p ? : 4); } @@ -2413,7 +2553,7 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2424,13 +2564,16 @@ store_fan_pulses(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->fan_pulses[nr] = val & 3; - reg = data->read_value(data, data->REG_FAN_PULSES[nr]); + err = nct6775_read_value(data, data->REG_FAN_PULSES[nr], ®); + if (err) + goto out; reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; - data->write_value(data, data->REG_FAN_PULSES[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_PULSES[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_fan_is_visible(struct kobject *kobj, @@ -2497,6 +2640,9 @@ show_temp_label(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); } @@ -2508,6 +2654,9 @@ show_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->temp[index][nr])); } @@ -2528,10 +2677,9 @@ store_temp(struct device *dev, struct device_attribute *attr, const char *buf, mutex_lock(&data->update_lock); data->temp[index][nr] = LM75_TEMP_TO_REG(val); - nct6775_write_temp(data, data->reg_temp[index][nr], - data->temp[index][nr]); + err = nct6775_write_temp(data, data->reg_temp[index][nr], data->temp[index][nr]); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2540,6 +2688,9 @@ show_temp_offset(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); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->temp_offset[sattr->index] * 1000); } @@ -2561,10 +2712,10 @@ store_temp_offset(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->temp_offset[nr] = val; - data->write_value(data, data->REG_TEMP_OFFSET[nr], val); + err = nct6775_write_value(data, data->REG_TEMP_OFFSET[nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2574,6 +2725,9 @@ show_temp_type(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); } @@ -2586,7 +2740,11 @@ store_temp_type(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 vbat, diode, vbit, dbit; + u8 vbit, dbit; + u16 vbat, diode; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2600,8 +2758,17 @@ store_temp_type(struct device *dev, struct device_attribute *attr, data->temp_type[nr] = val; vbit = 0x02 << nr; dbit = data->DIODE_MASK << nr; - vbat = data->read_value(data, data->REG_VBAT) & ~vbit; - diode = data->read_value(data, data->REG_DIODE) & ~dbit; + + err = nct6775_read_value(data, data->REG_VBAT, &vbat); + if (err) + goto out; + vbat &= ~vbit; + + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + goto out; + diode &= ~dbit; + switch (val) { case 1: /* CPU diode (diode, current mode) */ vbat |= vbit; @@ -2613,11 +2780,13 @@ store_temp_type(struct device *dev, struct device_attribute *attr, case 4: /* thermistor */ break; } - data->write_value(data, data->REG_VBAT, vbat); - data->write_value(data, data->REG_DIODE, diode); - + err = nct6775_write_value(data, data->REG_VBAT, vbat); + if (err) + goto out; + err = nct6775_write_value(data, data->REG_DIODE, diode); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_temp_is_visible(struct kobject *kobj, @@ -2707,6 +2876,9 @@ static ssize_t show_tsi_temp(struct device *dev, struct device_attribute *attr, struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + if (IS_ERR(data)) + return PTR_ERR(data); + return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); } @@ -2746,6 +2918,9 @@ show_pwm_mode(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); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_mode[sattr->index]); } @@ -2758,7 +2933,7 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, int nr = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2776,13 +2951,16 @@ store_pwm_mode(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->pwm_mode[nr] = val; - reg = data->read_value(data, data->REG_PWM_MODE[nr]); + err = nct6775_read_value(data, data->REG_PWM_MODE[nr], ®); + if (err) + goto out; reg &= ~data->PWM_MODE_MASK[nr]; if (!val) reg |= data->PWM_MODE_MASK[nr]; - data->write_value(data, data->REG_PWM_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2792,16 +2970,23 @@ show_pwm(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute_2 *sattr = to_sensor_dev_attr_2(attr); int nr = sattr->nr; int index = sattr->index; - int pwm; + int err; + u16 pwm; + + if (IS_ERR(data)) + return PTR_ERR(data); /* * For automatic fan control modes, show current pwm readings. * Otherwise, show the configured value. */ - if (index == 0 && data->pwm_enable[nr] > manual) - pwm = data->read_value(data, data->REG_PWM_READ[nr]); - else + if (index == 0 && data->pwm_enable[nr] > manual) { + err = nct6775_read_value(data, data->REG_PWM_READ[nr], &pwm); + if (err) + return err; + } else { pwm = data->pwm[index][nr]; + } return sprintf(buf, "%d\n", pwm); } @@ -2819,7 +3004,7 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, int maxval[7] = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -2828,16 +3013,21 @@ store_pwm(struct device *dev, struct device_attribute *attr, const char *buf, mutex_lock(&data->update_lock); data->pwm[index][nr] = val; - data->write_value(data, data->REG_PWM[index][nr], val); + err = nct6775_write_value(data, data->REG_PWM[index][nr], val); + if (err) + goto out; if (index == 2) { /* floor: disable if val == 0 */ - reg = data->read_value(data, data->REG_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0x7f; if (val) reg |= 0x80; - data->write_value(data, data->REG_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); } +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } /* Returns 0 if OK, -EINVAL otherwise */ @@ -2864,40 +3054,54 @@ static int check_trip_points(struct nct6775_data *data, int nr) return 0; } -static void pwm_update_registers(struct nct6775_data *data, int nr) +static int pwm_update_registers(struct nct6775_data *data, int nr) { - u8 reg; + u16 reg; + int err; switch (data->pwm_enable[nr]) { case off: case manual: break; case speed_cruise: - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; reg = (reg & ~data->tolerance_mask) | (data->target_speed_tolerance[nr] & data->tolerance_mask); - data->write_value(data, data->REG_FAN_MODE[nr], reg); - data->write_value(data, data->REG_TARGET[nr], - data->target_speed[nr] & 0xff); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; + err = nct6775_write_value(data, data->REG_TARGET[nr], + data->target_speed[nr] & 0xff); + if (err) + return err; if (data->REG_TOLERANCE_H) { reg = (data->target_speed[nr] >> 8) & 0x0f; reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; - data->write_value(data, - data->REG_TOLERANCE_H[nr], - reg); + err = nct6775_write_value(data, data->REG_TOLERANCE_H[nr], reg); + if (err) + return err; } break; case thermal_cruise: - data->write_value(data, data->REG_TARGET[nr], - data->target_temp[nr]); + err = nct6775_write_value(data, data->REG_TARGET[nr], data->target_temp[nr]); + if (err) + return err; fallthrough; default: - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; reg = (reg & ~data->tolerance_mask) | data->temp_tolerance[0][nr]; - data->write_value(data, data->REG_FAN_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; break; } + + return 0; } static ssize_t @@ -2906,6 +3110,9 @@ show_pwm_enable(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); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); } @@ -2943,15 +3150,22 @@ store_pwm_enable(struct device *dev, struct device_attribute *attr, * turn off pwm control: select manual mode, set pwm to maximum */ data->pwm[0][nr] = 255; - data->write_value(data, data->REG_PWM[0][nr], 255); + err = nct6775_write_value(data, data->REG_PWM[0][nr], 255); + if (err) + goto out; } - pwm_update_registers(data, nr); - reg = data->read_value(data, data->REG_FAN_MODE[nr]); + err = pwm_update_registers(data, nr); + if (err) + goto out; + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + goto out; reg &= 0x0f; reg |= pwm_enable_to_reg(val) << 4; - data->write_value(data, data->REG_FAN_MODE[nr], reg); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -2978,6 +3192,9 @@ show_pwm_temp_sel(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); } @@ -2989,7 +3206,11 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; unsigned long val; - int err, reg, src; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3002,13 +3223,16 @@ store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); src = data->temp_src[val - 1]; data->pwm_temp_sel[nr] = src; - reg = data->read_value(data, data->REG_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0xe0; reg |= src; - data->write_value(data, data->REG_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3019,6 +3243,9 @@ show_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return show_pwm_temp_sel_common(data, buf, data->pwm_weight_temp_sel[index]); } @@ -3031,7 +3258,11 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; unsigned long val; - int err, reg, src; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3047,19 +3278,24 @@ store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, if (val) { src = data->temp_src[val - 1]; data->pwm_weight_temp_sel[nr] = src; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0xe0; reg |= (src | 0x80); - data->write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); } else { data->pwm_weight_temp_sel[nr] = 0; - reg = data->read_value(data, data->REG_WEIGHT_TEMP_SEL[nr]); + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; reg &= 0x7f; - data->write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); } +out: mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3068,6 +3304,9 @@ show_target_temp(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); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); } @@ -3090,9 +3329,9 @@ store_target_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_temp[nr] = val; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3102,6 +3341,9 @@ show_target_speed(struct device *dev, struct device_attribute *attr, char *buf) struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", fan_from_reg16(data->target_speed[nr], data->fan_div[nr])); @@ -3127,9 +3369,9 @@ store_target_speed(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_speed[nr] = speed; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3141,6 +3383,9 @@ show_temp_tolerance(struct device *dev, struct device_attribute *attr, int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); } @@ -3165,13 +3410,11 @@ store_temp_tolerance(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->temp_tolerance[index][nr] = val; if (index) - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); else - data->write_value(data, - data->REG_CRITICAL_TEMP_TOLERANCE[nr], - val); + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } /* @@ -3188,8 +3431,12 @@ show_speed_tolerance(struct device *dev, struct device_attribute *attr, struct nct6775_data *data = nct6775_update_device(dev); struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); int nr = sattr->index; - int target = data->target_speed[nr]; - int tolerance = 0; + int target, tolerance = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + target = data->target_speed[nr]; if (target) { int low = target - data->target_speed_tolerance[nr]; @@ -3239,9 +3486,9 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->target_speed_tolerance[nr] = val; - pwm_update_registers(data, nr); + err = pwm_update_registers(data, nr); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } SENSOR_TEMPLATE_2(pwm, "pwm%d", S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); @@ -3268,6 +3515,9 @@ show_weight_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); } @@ -3290,9 +3540,9 @@ store_weight_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->weight_temp[index][nr] = val; - data->write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", S_IWUSR | S_IRUGO, @@ -3316,6 +3566,9 @@ show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int index = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", step_time_from_reg(data->fan_time[index][nr], data->pwm_mode[nr])); @@ -3339,9 +3592,9 @@ store_fan_time(struct device *dev, struct device_attribute *attr, val = step_time_to_reg(val, data->pwm_mode[nr]); mutex_lock(&data->update_lock); data->fan_time[index][nr] = val; - data->write_value(data, data->REG_FAN_TIME[index][nr], val); + err = nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3350,6 +3603,9 @@ show_auto_pwm(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); + if (IS_ERR(data)) + return PTR_ERR(data); + return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); } @@ -3363,7 +3619,7 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, int point = sattr->index; unsigned long val; int err; - u8 reg; + u16 reg; err = kstrtoul(buf, 10, &val); if (err < 0) @@ -3381,21 +3637,20 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->auto_pwm[nr][point] = val; if (point < data->auto_pwm_num) { - data->write_value(data, - NCT6775_AUTO_PWM(data, nr, point), - data->auto_pwm[nr][point]); + err = nct6775_write_value(data, NCT6775_AUTO_PWM(data, nr, point), + data->auto_pwm[nr][point]); } else { switch (data->kind) { case nct6775: /* disable if needed (pwm == 0) */ - reg = data->read_value(data, - NCT6775_REG_CRITICAL_ENAB[nr]); + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[nr], ®); + if (err) + break; if (val) reg |= 0x02; else reg &= ~0x02; - data->write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], - reg); + err = nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], reg); break; case nct6776: break; /* always enabled, nothing to do */ @@ -3409,22 +3664,22 @@ store_auto_pwm(struct device *dev, struct device_attribute *attr, case nct6796: case nct6797: case nct6798: - data->write_value(data, data->REG_CRITICAL_PWM[nr], - val); - reg = data->read_value(data, - data->REG_CRITICAL_PWM_ENABLE[nr]); + err = nct6775_write_value(data, data->REG_CRITICAL_PWM[nr], val); + if (err) + break; + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], ®); + if (err) + break; if (val == 255) reg &= ~data->CRITICAL_PWM_ENABLE_MASK; else reg |= data->CRITICAL_PWM_ENABLE_MASK; - data->write_value(data, - data->REG_CRITICAL_PWM_ENABLE[nr], - reg); + err = nct6775_write_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], reg); break; } } mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static ssize_t @@ -3435,6 +3690,9 @@ show_auto_temp(struct device *dev, struct device_attribute *attr, char *buf) int nr = sattr->nr; int point = sattr->index; + if (IS_ERR(data)) + return PTR_ERR(data); + /* * We don't know for sure if the temperature is signed or unsigned. * Assume it is unsigned. @@ -3462,15 +3720,14 @@ store_auto_temp(struct device *dev, struct device_attribute *attr, mutex_lock(&data->update_lock); data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); if (point < data->auto_pwm_num) { - data->write_value(data, - NCT6775_AUTO_TEMP(data, nr, point), - data->auto_temp[nr][point]); + err = nct6775_write_value(data, NCT6775_AUTO_TEMP(data, nr, point), + data->auto_temp[nr][point]); } else { - data->write_value(data, data->REG_CRITICAL_TEMP[nr], - data->auto_temp[nr][point]); + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], + data->auto_temp[nr][point]); } mutex_unlock(&data->update_lock); - return count; + return err ? : count; } static umode_t nct6775_pwm_is_visible(struct kobject *kobj, @@ -3719,16 +3976,21 @@ static const struct attribute_group nct6775_group_other = { .is_visible = nct6775_other_is_visible, }; -static inline void nct6775_init_device(struct nct6775_data *data) +static inline int nct6775_init_device(struct nct6775_data *data) { - int i; - u8 tmp, diode; + int i, err; + u16 tmp, diode; /* Start monitoring if needed */ if (data->REG_CONFIG) { - tmp = data->read_value(data, data->REG_CONFIG); - if (!(tmp & 0x01)) - data->write_value(data, data->REG_CONFIG, tmp | 0x01); + err = nct6775_read_value(data, data->REG_CONFIG, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); + if (err) + return err; + } } /* Enable temperature sensors if needed */ @@ -3737,18 +3999,29 @@ static inline void nct6775_init_device(struct nct6775_data *data) continue; if (!data->reg_temp_config[i]) continue; - tmp = data->read_value(data, data->reg_temp_config[i]); - if (tmp & 0x01) - data->write_value(data, data->reg_temp_config[i], - tmp & 0xfe); + err = nct6775_read_value(data, data->reg_temp_config[i], &tmp); + if (err) + return err; + if (tmp & 0x01) { + err = nct6775_write_value(data, data->reg_temp_config[i], tmp & 0xfe); + if (err) + return err; + } } /* Enable VBAT monitoring if needed */ - tmp = data->read_value(data, data->REG_VBAT); - if (!(tmp & 0x01)) - data->write_value(data, data->REG_VBAT, tmp | 0x01); + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); + if (err) + return err; + } - diode = data->read_value(data, data->REG_DIODE); + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + return err; for (i = 0; i < data->temp_fixed_num; i++) { if (!(data->have_temp_fixed & BIT(i))) @@ -3759,6 +4032,8 @@ static inline void nct6775_init_device(struct nct6775_data *data) else /* thermistor */ data->temp_type[i] = 4; } + + return 0; } static void @@ -3982,18 +4257,20 @@ nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); } -static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, - int *available, int *mask) +static int add_temp_sensors(struct nct6775_data *data, const u16 *regp, + int *available, int *mask) { - int i; - u8 src; + int i, err; + u16 src; for (i = 0; i < data->pwm_num && *available; i++) { int index; if (!regp[i]) continue; - src = data->read_value(data, regp[i]); + err = nct6775_read_value(data, regp[i], &src); + if (err) + return err; src &= 0x1f; if (!src || (*mask & BIT(src))) continue; @@ -4001,12 +4278,30 @@ static void add_temp_sensors(struct nct6775_data *data, const u16 *regp, continue; index = __ffs(*available); - data->write_value(data, data->REG_TEMP_SOURCE[index], src); + err = nct6775_write_value(data, data->REG_TEMP_SOURCE[index], src); + if (err) + return err; *available &= ~BIT(index); *mask |= BIT(src); } + + return 0; } +static const struct regmap_config nct6775_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_reg_read, + .reg_write = nct6775_reg_write, +}; + +static const struct regmap_config nct6775_wmi_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_wmi_reg_read, + .reg_write = nct6775_wmi_reg_write, +}; + static int nct6775_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -4014,7 +4309,8 @@ static int nct6775_probe(struct platform_device *pdev) struct nct6775_data *data; struct resource *res; int i, s, err = 0; - int src, mask, available; + int mask, available; + u16 src; const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; @@ -4023,6 +4319,7 @@ static int nct6775_probe(struct platform_device *pdev) struct attribute_group *group; struct device *hwmon_dev; struct sensor_template_group tsi_temp_tg; + const struct regmap_config *regmapcfg; int num_attr_groups = 0; if (sio_data->access == access_direct) { @@ -4042,13 +4339,15 @@ static int nct6775_probe(struct platform_device *pdev) if (sio_data->access == access_direct) { data->addr = res->start; - data->read_value = nct6775_read_value; - data->write_value = nct6775_write_value; + regmapcfg = &nct6775_regmap_config; } else { - data->read_value = nct6775_wmi_read_value; - data->write_value = nct6775_wmi_write_value; + regmapcfg = &nct6775_wmi_regmap_config; } + data->regmap = devm_regmap_init(dev, NULL, data, regmapcfg); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + mutex_init(&data->update_lock); data->name = nct6775_device_names[data->kind]; data->bank = 0xff; /* Force initial bank selection */ @@ -4596,7 +4895,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; if (!src || (mask & BIT(src))) available |= BIT(i); @@ -4607,8 +4909,12 @@ static int nct6775_probe(struct platform_device *pdev) * Now find unmonitored temperature registers and enable monitoring * if additional monitoring registers are available. */ - add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); - add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + err = add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); + if (err) + return err; + err = add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + if (err) + return err; mask = 0; s = NUM_TEMP_FIXED; /* First dynamic temperature attribute */ @@ -4616,7 +4922,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SOURCE[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; if (!src || (mask & BIT(src))) continue; @@ -4676,7 +4985,10 @@ static int nct6775_probe(struct platform_device *pdev) if (reg_temp_mon[i] == 0) continue; - src = data->read_value(data, data->REG_TEMP_SEL[i]) & 0x1f; + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], &src); + if (err) + return err; + src &= 0x1f; if (!src) continue; @@ -4760,12 +5072,19 @@ static int nct6775_probe(struct platform_device *pdev) /* Check which TSIx_TEMP registers are active */ for (i = 0; i < num_reg_tsi_temp; i++) { - if (data->read_value(data, data->REG_TSI_TEMP[i])) + u16 tmp; + + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], &tmp); + if (err) + return err; + if (tmp) data->have_tsi_temp |= BIT(i); } /* Initialize the chip */ - nct6775_init_device(data); + err = nct6775_init_device(data); + if (err) + return err; err = sio_data->sio_enter(sio_data); if (err) @@ -4841,7 +5160,9 @@ static int nct6775_probe(struct platform_device *pdev) sio_data->sio_exit(sio_data); /* Read fan clock dividers immediately */ - nct6775_init_fan_common(dev, data); + err = nct6775_init_fan_common(dev, data); + if (err) + return err; /* Register sysfs hooks */ group = nct6775_create_attr_group(dev, &nct6775_pwm_template_group, @@ -4904,17 +5225,33 @@ static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) static int __maybe_unused nct6775_suspend(struct device *dev) { + int err; + u16 tmp; struct nct6775_data *data = nct6775_update_device(dev); + if (IS_ERR(data)) + return PTR_ERR(data); + mutex_lock(&data->update_lock); - data->vbat = data->read_value(data, data->REG_VBAT); + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + goto out; + data->vbat = tmp; if (data->kind == nct6775) { - data->fandiv1 = data->read_value(data, NCT6775_REG_FANDIV1); - data->fandiv2 = data->read_value(data, NCT6775_REG_FANDIV2); + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &tmp); + if (err) + goto out; + data->fandiv1 = tmp; + + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &tmp); + if (err) + goto out; + data->fandiv2 = tmp; } +out: mutex_unlock(&data->update_lock); - return 0; + return err; } static int __maybe_unused nct6775_resume(struct device *dev) @@ -4949,18 +5286,21 @@ static int __maybe_unused nct6775_resume(struct device *dev) if (!(data->have_in & BIT(i))) continue; - data->write_value(data, data->REG_IN_MINMAX[0][i], - data->in[i][1]); - data->write_value(data, data->REG_IN_MINMAX[1][i], - data->in[i][2]); + err = nct6775_write_value(data, data->REG_IN_MINMAX[0][i], data->in[i][1]); + if (err) + goto abort; + err = nct6775_write_value(data, data->REG_IN_MINMAX[1][i], data->in[i][2]); + if (err) + goto abort; } for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { if (!(data->has_fan_min & BIT(i))) continue; - data->write_value(data, data->REG_FAN_MIN[i], - data->fan_min[i]); + err = nct6775_write_value(data, data->REG_FAN_MIN[i], data->fan_min[i]); + if (err) + goto abort; } for (i = 0; i < NUM_TEMP; i++) { @@ -4968,16 +5308,23 @@ static int __maybe_unused nct6775_resume(struct device *dev) continue; for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) - if (data->reg_temp[j][i]) - nct6775_write_temp(data, data->reg_temp[j][i], - data->temp[j][i]); + if (data->reg_temp[j][i]) { + err = nct6775_write_temp(data, data->reg_temp[j][i], + data->temp[j][i]); + if (err) + goto abort; + } } /* Restore other settings */ - data->write_value(data, data->REG_VBAT, data->vbat); + err = nct6775_write_value(data, data->REG_VBAT, data->vbat); + if (err) + goto abort; if (data->kind == nct6775) { - data->write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); - data->write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); + err = nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); + if (err) + goto abort; + err = nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); } abort: -- cgit v1.2.3 From 3c7e4935d46803f009e344546e41e777df7537b1 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Tue, 26 Apr 2022 18:01:50 -0700 Subject: hwmon: (nct6775) Rearrange attr-group initialization We now track the number of attribute groups in nct6775_data, as a measure to simplify handling differences in the set of enabled attribute groups between nct6775 drivers (platform & i2c). As a side effect, we also reduce the amount of IS_ERR()/PTR_ERR() boilerplate a bit. Signed-off-by: Zev Weiss Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220427010154.29749-4-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 84 ++++++++++++++++++++++++------------------------- 1 file changed, 42 insertions(+), 42 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index be99a1890ccd..36bdbb176601 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -1199,6 +1199,7 @@ struct nct6775_data { const char *name; const struct attribute_group *groups[7]; + u8 num_groups; u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, * 3=temp_crit, 4=temp_lcrit @@ -1404,10 +1405,18 @@ struct sensor_template_group { int base; }; -static struct attribute_group * -nct6775_create_attr_group(struct device *dev, - const struct sensor_template_group *tg, - int repeat) +static int nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group) +{ + /* Need to leave a NULL terminator at the end of data->groups */ + if (data->num_groups == ARRAY_SIZE(data->groups) - 1) + return -ENOBUFS; + + data->groups[data->num_groups++] = group; + return 0; +} + +static int nct6775_add_template_attr_group(struct device *dev, struct nct6775_data *data, + const struct sensor_template_group *tg, int repeat) { struct attribute_group *group; struct sensor_device_attr_u *su; @@ -1418,28 +1427,28 @@ nct6775_create_attr_group(struct device *dev, int i, count; if (repeat <= 0) - return ERR_PTR(-EINVAL); + return -EINVAL; t = tg->templates; for (count = 0; *t; t++, count++) ; if (count == 0) - return ERR_PTR(-EINVAL); + return -EINVAL; group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); if (group == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), GFP_KERNEL); if (attrs == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), GFP_KERNEL); if (su == NULL) - return ERR_PTR(-ENOMEM); + return -ENOMEM; group->attrs = attrs; group->is_visible = tg->is_visible; @@ -1477,7 +1486,7 @@ nct6775_create_attr_group(struct device *dev, } } - return group; + return nct6775_add_attr_group(data, group); } static bool is_word_sized(struct nct6775_data *data, u16 reg) @@ -4316,11 +4325,9 @@ static int nct6775_probe(struct platform_device *pdev) const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; u8 cr2a; - struct attribute_group *group; struct device *hwmon_dev; struct sensor_template_group tsi_temp_tg; const struct regmap_config *regmapcfg; - int num_attr_groups = 0; if (sio_data->access == access_direct) { res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -5165,46 +5172,39 @@ static int nct6775_probe(struct platform_device *pdev) return err; /* Register sysfs hooks */ - group = nct6775_create_attr_group(dev, &nct6775_pwm_template_group, - data->pwm_num); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; - - group = nct6775_create_attr_group(dev, &nct6775_in_template_group, - fls(data->have_in)); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; - - group = nct6775_create_attr_group(dev, &nct6775_fan_template_group, - fls(data->has_fan)); - if (IS_ERR(group)) - return PTR_ERR(group); + err = nct6775_add_template_attr_group(dev, data, &nct6775_pwm_template_group, + data->pwm_num); + if (err) + return err; - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &nct6775_in_template_group, + fls(data->have_in)); + if (err) + return err; - group = nct6775_create_attr_group(dev, &nct6775_temp_template_group, - fls(data->have_temp)); - if (IS_ERR(group)) - return PTR_ERR(group); + err = nct6775_add_template_attr_group(dev, data, &nct6775_fan_template_group, + fls(data->has_fan)); + if (err) + return err; - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &nct6775_temp_template_group, + fls(data->have_temp)); + if (err) + return err; if (data->have_tsi_temp) { tsi_temp_tg.templates = nct6775_tsi_temp_template; tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; tsi_temp_tg.base = fls(data->have_temp) + 1; - group = nct6775_create_attr_group(dev, &tsi_temp_tg, fls(data->have_tsi_temp)); - if (IS_ERR(group)) - return PTR_ERR(group); - - data->groups[num_attr_groups++] = group; + err = nct6775_add_template_attr_group(dev, data, &tsi_temp_tg, + fls(data->have_tsi_temp)); + if (err) + return err; } - data->groups[num_attr_groups++] = &nct6775_group_other; + err = nct6775_add_attr_group(data, &nct6775_group_other); + if (err) + return err; hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, data, data->groups); -- cgit v1.2.3 From bd2e82bd4f4839f70c00a480e0133dc9650d7211 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Tue, 26 Apr 2022 18:01:51 -0700 Subject: hwmon: (nct6775) Add read-only mode When enabled, all write bits are removed from the modes of all sysfs attribute files. This provides a bit of infrastructure for the upcoming i2c version of this driver, which should generally avoid writes to device registers so as not to interfere with simultaneous use of the device via the LPC interface. Signed-off-by: Zev Weiss Reviewed-by: Joel Stanley Link: https://lore.kernel.org/r/20220427010154.29749-5-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 36bdbb176601..99b4e308a053 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -1348,6 +1348,7 @@ struct nct6775_data { u8 sio_reg_enable; struct regmap *regmap; + bool read_only; }; struct sensor_device_template { @@ -1405,6 +1406,11 @@ struct sensor_template_group { int base; }; +static inline umode_t nct6775_attr_mode(struct nct6775_data *data, struct attribute *attr) +{ + return data->read_only ? (attr->mode & ~0222) : attr->mode; +} + static int nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group) { /* Need to leave a NULL terminator at the end of data->groups */ @@ -2371,7 +2377,7 @@ static umode_t nct6775_in_is_visible(struct kobject *kobj, if (!(data->have_in & BIT(in))) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); @@ -2607,7 +2613,7 @@ static umode_t nct6775_fan_is_visible(struct kobject *kobj, if (nr == 5 && data->kind != nct6775) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); @@ -2834,7 +2840,7 @@ static umode_t nct6775_temp_is_visible(struct kobject *kobj, if (nr > 7 && !(data->have_temp_fixed & BIT(temp))) return 0; - return attr->mode; + return nct6775_attr_mode(data, attr); } SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temp, NULL, 0, 0); @@ -2908,7 +2914,7 @@ static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribut struct nct6775_data *data = dev_get_drvdata(dev); int temp = index / 2; - return (data->have_tsi_temp & BIT(temp)) ? attr->mode : 0; + return (data->have_tsi_temp & BIT(temp)) ? nct6775_attr_mode(data, attr) : 0; } /* @@ -3766,7 +3772,7 @@ static umode_t nct6775_pwm_is_visible(struct kobject *kobj, if (api > data->auto_pwm_num) return 0; } - return attr->mode; + return nct6775_attr_mode(data, attr); } SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", S_IWUSR | S_IRUGO, @@ -3961,7 +3967,7 @@ static umode_t nct6775_other_is_visible(struct kobject *kobj, return 0; } - return attr->mode; + return nct6775_attr_mode(data, attr); } /* -- cgit v1.2.3 From ae0d7227741f6791f056f906625ffbd5fe95fb2c Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Tue, 26 Apr 2022 18:01:52 -0700 Subject: hwmon: (nct6775) Convert S_I* permissions macros to octal Checkpatch has been warning about these for a while; the octal versions are both more comprehensible and more concise. Signed-off-by: Zev Weiss Link: https://lore.kernel.org/r/20220427010154.29749-6-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775.c | 159 +++++++++++++++++++----------------------------- 1 file changed, 64 insertions(+), 95 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c index 99b4e308a053..5e741bcf2645 100644 --- a/drivers/hwmon/nct6775.c +++ b/drivers/hwmon/nct6775.c @@ -2380,14 +2380,11 @@ static umode_t nct6775_in_is_visible(struct kobject *kobj, return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(in_input, "in%d_input", S_IRUGO, show_in_reg, NULL, 0, 0); -SENSOR_TEMPLATE(in_alarm, "in%d_alarm", S_IRUGO, show_alarm, NULL, 0); -SENSOR_TEMPLATE(in_beep, "in%d_beep", S_IWUSR | S_IRUGO, show_beep, store_beep, - 0); -SENSOR_TEMPLATE_2(in_min, "in%d_min", S_IWUSR | S_IRUGO, show_in_reg, - store_in_reg, 0, 1); -SENSOR_TEMPLATE_2(in_max, "in%d_max", S_IWUSR | S_IRUGO, show_in_reg, - store_in_reg, 0, 2); +SENSOR_TEMPLATE_2(in_input, "in%d_input", 0444, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE(in_alarm, "in%d_alarm", 0444, show_alarm, NULL, 0); +SENSOR_TEMPLATE(in_beep, "in%d_beep", 0644, show_beep, store_beep, 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", 0644, show_in_reg, store_in_reg, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", 0644, show_in_reg, store_in_reg, 0, 2); /* * nct6775_in_is_visible uses the index into the following array @@ -2616,16 +2613,12 @@ static umode_t nct6775_fan_is_visible(struct kobject *kobj, return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE(fan_input, "fan%d_input", S_IRUGO, show_fan, NULL, 0); -SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", S_IRUGO, show_alarm, NULL, - FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_beep, "fan%d_beep", S_IWUSR | S_IRUGO, show_beep, - store_beep, FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", S_IWUSR | S_IRUGO, show_fan_pulses, - store_fan_pulses, 0); -SENSOR_TEMPLATE(fan_min, "fan%d_min", S_IWUSR | S_IRUGO, show_fan_min, - store_fan_min, 0); -SENSOR_TEMPLATE(fan_div, "fan%d_div", S_IRUGO, show_fan_div, NULL, 0); +SENSOR_TEMPLATE(fan_input, "fan%d_input", 0444, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", 0444, show_alarm, NULL, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_beep, "fan%d_beep", 0644, show_beep, store_beep, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", 0644, show_fan_pulses, store_fan_pulses, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", 0644, show_fan_min, store_fan_min, 0); +SENSOR_TEMPLATE(fan_div, "fan%d_div", 0444, show_fan_div, NULL, 0); /* * nct6775_fan_is_visible uses the index into the following array @@ -2843,23 +2836,16 @@ static umode_t nct6775_temp_is_visible(struct kobject *kobj, return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(temp_input, "temp%d_input", S_IRUGO, show_temp, NULL, 0, 0); -SENSOR_TEMPLATE(temp_label, "temp%d_label", S_IRUGO, show_temp_label, NULL, 0); -SENSOR_TEMPLATE_2(temp_max, "temp%d_max", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 1); -SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", S_IRUGO | S_IWUSR, - show_temp, store_temp, 0, 2); -SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 3); -SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", S_IRUGO | S_IWUSR, show_temp, - store_temp, 0, 4); -SENSOR_TEMPLATE(temp_offset, "temp%d_offset", S_IRUGO | S_IWUSR, - show_temp_offset, store_temp_offset, 0); -SENSOR_TEMPLATE(temp_type, "temp%d_type", S_IRUGO | S_IWUSR, show_temp_type, - store_temp_type, 0); -SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", S_IRUGO, show_temp_alarm, NULL, 0); -SENSOR_TEMPLATE(temp_beep, "temp%d_beep", S_IRUGO | S_IWUSR, show_temp_beep, - store_temp_beep, 0); +SENSOR_TEMPLATE_2(temp_input, "temp%d_input", 0444, show_temp, NULL, 0, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", 0444, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", 0644, show_temp, store_temp, 0, 1); +SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", 0644, show_temp, store_temp, 0, 2); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", 0644, show_temp, store_temp, 0, 3); +SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", 0644, show_temp, store_temp, 0, 4); +SENSOR_TEMPLATE(temp_offset, "temp%d_offset", 0644, show_temp_offset, store_temp_offset, 0); +SENSOR_TEMPLATE(temp_type, "temp%d_type", 0644, show_temp_type, store_temp_type, 0); +SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", 0444, show_temp_alarm, NULL, 0); +SENSOR_TEMPLATE(temp_beep, "temp%d_beep", 0644, show_temp_beep, store_temp_beep, 0); /* * nct6775_temp_is_visible uses the index into the following array @@ -3506,19 +3492,14 @@ store_speed_tolerance(struct device *dev, struct device_attribute *attr, return err ? : count; } -SENSOR_TEMPLATE_2(pwm, "pwm%d", S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 0); -SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", S_IWUSR | S_IRUGO, show_pwm_mode, - store_pwm_mode, 0); -SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", S_IWUSR | S_IRUGO, show_pwm_enable, - store_pwm_enable, 0); -SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", S_IWUSR | S_IRUGO, - show_pwm_temp_sel, store_pwm_temp_sel, 0); -SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", S_IWUSR | S_IRUGO, - show_target_temp, store_target_temp, 0); -SENSOR_TEMPLATE(fan_target, "fan%d_target", S_IWUSR | S_IRUGO, - show_target_speed, store_target_speed, 0); -SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", S_IWUSR | S_IRUGO, - show_speed_tolerance, store_speed_tolerance, 0); +SENSOR_TEMPLATE_2(pwm, "pwm%d", 0644, show_pwm, store_pwm, 0, 0); +SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", 0644, show_pwm_mode, store_pwm_mode, 0); +SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", 0644, show_pwm_enable, store_pwm_enable, 0); +SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", 0644, show_pwm_temp_sel, store_pwm_temp_sel, 0); +SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", 0644, show_target_temp, store_target_temp, 0); +SENSOR_TEMPLATE(fan_target, "fan%d_target", 0644, show_target_speed, store_target_speed, 0); +SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", 0644, show_speed_tolerance, + store_speed_tolerance, 0); /* Smart Fan registers */ @@ -3560,18 +3541,16 @@ store_weight_temp(struct device *dev, struct device_attribute *attr, return err ? : count; } -SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", S_IWUSR | S_IRUGO, - show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); +SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", 0644, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); SENSOR_TEMPLATE_2(pwm_weight_temp_step, "pwm%d_weight_temp_step", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 0); + 0644, show_weight_temp, store_weight_temp, 0, 0); SENSOR_TEMPLATE_2(pwm_weight_temp_step_tol, "pwm%d_weight_temp_step_tol", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 1); + 0644, show_weight_temp, store_weight_temp, 0, 1); SENSOR_TEMPLATE_2(pwm_weight_temp_step_base, "pwm%d_weight_temp_step_base", - S_IWUSR | S_IRUGO, show_weight_temp, store_weight_temp, 0, 2); -SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", - S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 5); -SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", - S_IWUSR | S_IRUGO, show_pwm, store_pwm, 0, 6); + 0644, show_weight_temp, store_weight_temp, 0, 2); +SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", 0644, show_pwm, store_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", 0644, show_pwm, store_pwm, 0, 6); static ssize_t show_fan_time(struct device *dev, struct device_attribute *attr, char *buf) @@ -3775,62 +3754,56 @@ static umode_t nct6775_pwm_is_visible(struct kobject *kobj, return nct6775_attr_mode(data, attr); } -SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", S_IWUSR | S_IRUGO, - show_fan_time, store_fan_time, 0, 0); -SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", 0644, show_fan_time, store_fan_time, 0, 0); +SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", 0644, show_fan_time, store_fan_time, 0, 1); -SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", 0644, show_fan_time, store_fan_time, 0, 2); -SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 1); -SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 2); -SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", S_IWUSR | S_IRUGO, +SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", 0644, show_pwm, store_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", 0644, show_pwm, store_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", 0644, show_temp_tolerance, store_temp_tolerance, 0, 0); SENSOR_TEMPLATE_2(pwm_crit_temp_tolerance, "pwm%d_crit_temp_tolerance", - S_IWUSR | S_IRUGO, show_temp_tolerance, store_temp_tolerance, - 0, 1); + 0644, show_temp_tolerance, store_temp_tolerance, 0, 1); -SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", S_IWUSR | S_IRUGO, show_pwm, store_pwm, - 0, 3); +SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", 0644, show_pwm, store_pwm, 0, 3); -SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", S_IWUSR | S_IRUGO, show_pwm, - store_pwm, 0, 4); +SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", 0644, show_pwm, store_pwm, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point1_pwm, "pwm%d_auto_point1_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 0); + 0644, show_auto_pwm, store_auto_pwm, 0, 0); SENSOR_TEMPLATE_2(pwm_auto_point1_temp, "pwm%d_auto_point1_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 0); + 0644, show_auto_temp, store_auto_temp, 0, 0); SENSOR_TEMPLATE_2(pwm_auto_point2_pwm, "pwm%d_auto_point2_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 1); + 0644, show_auto_pwm, store_auto_pwm, 0, 1); SENSOR_TEMPLATE_2(pwm_auto_point2_temp, "pwm%d_auto_point2_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 1); + 0644, show_auto_temp, store_auto_temp, 0, 1); SENSOR_TEMPLATE_2(pwm_auto_point3_pwm, "pwm%d_auto_point3_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 2); + 0644, show_auto_pwm, store_auto_pwm, 0, 2); SENSOR_TEMPLATE_2(pwm_auto_point3_temp, "pwm%d_auto_point3_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 2); + 0644, show_auto_temp, store_auto_temp, 0, 2); SENSOR_TEMPLATE_2(pwm_auto_point4_pwm, "pwm%d_auto_point4_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 3); + 0644, show_auto_pwm, store_auto_pwm, 0, 3); SENSOR_TEMPLATE_2(pwm_auto_point4_temp, "pwm%d_auto_point4_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 3); + 0644, show_auto_temp, store_auto_temp, 0, 3); SENSOR_TEMPLATE_2(pwm_auto_point5_pwm, "pwm%d_auto_point5_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 4); + 0644, show_auto_pwm, store_auto_pwm, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point5_temp, "pwm%d_auto_point5_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 4); + 0644, show_auto_temp, store_auto_temp, 0, 4); SENSOR_TEMPLATE_2(pwm_auto_point6_pwm, "pwm%d_auto_point6_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 5); + 0644, show_auto_pwm, store_auto_pwm, 0, 5); SENSOR_TEMPLATE_2(pwm_auto_point6_temp, "pwm%d_auto_point6_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 5); + 0644, show_auto_temp, store_auto_temp, 0, 5); SENSOR_TEMPLATE_2(pwm_auto_point7_pwm, "pwm%d_auto_point7_pwm", - S_IWUSR | S_IRUGO, show_auto_pwm, store_auto_pwm, 0, 6); + 0644, show_auto_pwm, store_auto_pwm, 0, 6); SENSOR_TEMPLATE_2(pwm_auto_point7_temp, "pwm%d_auto_point7_temp", - S_IWUSR | S_IRUGO, show_auto_temp, store_auto_temp, 0, 6); + 0644, show_auto_temp, store_auto_temp, 0, 6); /* * nct6775_pwm_is_visible uses the index into the following array @@ -3937,16 +3910,12 @@ error: return count; } -static SENSOR_DEVICE_ATTR(intrusion0_alarm, S_IWUSR | S_IRUGO, show_alarm, - clear_caseopen, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_alarm, S_IWUSR | S_IRUGO, show_alarm, +static SENSOR_DEVICE_ATTR(intrusion0_alarm, 0644, show_alarm, clear_caseopen, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_alarm, 0644, show_alarm, clear_caseopen, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(intrusion0_beep, S_IWUSR | S_IRUGO, show_beep, - store_beep, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_beep, S_IWUSR | S_IRUGO, show_beep, - store_beep, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(beep_enable, S_IWUSR | S_IRUGO, show_beep, - store_beep, BEEP_ENABLE_BASE); +static SENSOR_DEVICE_ATTR(intrusion0_beep, 0644, show_beep, store_beep, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_beep, 0644, show_beep, store_beep, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(beep_enable, 0644, show_beep, store_beep, BEEP_ENABLE_BASE); static umode_t nct6775_other_is_visible(struct kobject *kobj, struct attribute *attr, int index) -- cgit v1.2.3 From c3963bc0a0cf9ecb205a9d4976eb92b6df2fa3fd Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Tue, 26 Apr 2022 18:01:53 -0700 Subject: hwmon: (nct6775) Split core and platform driver This splits the nct6775 driver into an interface-independent core and a separate platform driver that wraps inb/outb port I/O (or asuswmi methods) around that core. Signed-off-by: Zev Weiss Tested-by: Renze Nicolai Link: https://lore.kernel.org/r/20220427010154.29749-7-zev@bewilderbeest.net Tested-by: Oleksandr Natalenko Signed-off-by: Guenter Roeck --- MAINTAINERS | 6 +- drivers/hwmon/Kconfig | 14 +- drivers/hwmon/Makefile | 2 + drivers/hwmon/nct6775-core.c | 4205 ++++++++++++++++++++++++++++ drivers/hwmon/nct6775-platform.c | 1226 +++++++++ drivers/hwmon/nct6775.c | 5606 -------------------------------------- drivers/hwmon/nct6775.h | 252 ++ 7 files changed, 5702 insertions(+), 5609 deletions(-) create mode 100644 drivers/hwmon/nct6775-core.c create mode 100644 drivers/hwmon/nct6775-platform.c delete mode 100644 drivers/hwmon/nct6775.c create mode 100644 drivers/hwmon/nct6775.h (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 7ead62833c09..6ee5f2cf4ad2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13537,12 +13537,14 @@ M: Samuel Mendoza-Jonas S: Maintained F: net/ncsi/ -NCT6775 HARDWARE MONITOR DRIVER +NCT6775 HARDWARE MONITOR DRIVER - CORE & PLATFORM DRIVER M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained F: Documentation/hwmon/nct6775.rst -F: drivers/hwmon/nct6775.c +F: drivers/hwmon/nct6775-core.c +F: drivers/hwmon/nct6775-platform.c +F: drivers/hwmon/nct6775.h NETDEVSIM M: Jakub Kicinski diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 3db1b01a6666..01a73a0f378a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1462,11 +1462,23 @@ config SENSORS_NCT6683 This driver can also be built as a module. If so, the module will be called nct6683. +config SENSORS_NCT6775_CORE + tristate + select REGMAP + help + This module contains common code shared by the platform and + i2c versions of the nct6775 driver; it is not useful on its + own. + + If built as a module, the module will be called + nct6775-core. + config SENSORS_NCT6775 - tristate "Nuvoton NCT6775F and compatibles" + tristate "Platform driver for Nuvoton NCT6775F and compatibles" depends on !PPC depends on ACPI_WMI || ACPI_WMI=n select HWMON_VID + select SENSORS_NCT6775_CORE help If you say yes here you get support for the hardware monitoring functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D, diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 8a03289e2aa4..93f2b774cc5e 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -154,6 +154,8 @@ obj-$(CONFIG_SENSORS_MLXREG_FAN) += mlxreg-fan.o obj-$(CONFIG_SENSORS_MENF21BMC_HWMON) += menf21bmc_hwmon.o obj-$(CONFIG_SENSORS_MR75203) += mr75203.o obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o +obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o +nct6775-objs := nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o diff --git a/drivers/hwmon/nct6775-core.c b/drivers/hwmon/nct6775-core.c new file mode 100644 index 000000000000..446964cbae4c --- /dev/null +++ b/drivers/hwmon/nct6775-core.c @@ -0,0 +1,4205 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * nct6775 - Driver for the hardware monitoring functionality of + * Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2012 Guenter Roeck + * + * Derived from w83627ehf driver + * Copyright (C) 2005-2012 Jean Delvare + * Copyright (C) 2006 Yuan Mu (Winbond), + * Rudolf Marek + * David Hubbard + * Daniel J Blueman + * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) + * + * Shamelessly ripped from the w83627hf driver + * Copyright (C) 2003 Mark Studebaker + * + * Supports the following chips: + * + * Chip #vin #fan #pwm #temp chip IDs man ID + * nct6106d 9 3 3 6+3 0xc450 0xc1 0x5ca3 + * nct6116d 9 5 5 3+3 0xd280 0xc1 0x5ca3 + * 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 + * nct6791d 15 6 6 2+6 0xc800 0xc1 0x5ca3 + * nct6792d 15 6 6 2+6 0xc910 0xc1 0x5ca3 + * nct6793d 15 6 6 2+6 0xd120 0xc1 0x5ca3 + * nct6795d 14 6 6 2+6 0xd350 0xc1 0x5ca3 + * nct6796d 14 7 7 2+6 0xd420 0xc1 0x5ca3 + * nct6797d 14 7 7 2+6 0xd450 0xc1 0x5ca3 + * (0xd451) + * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 + * (0xd429) + * + * #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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lm75.h" +#include "nct6775.h" + +#undef DEFAULT_SYMBOL_NAMESPACE +#define DEFAULT_SYMBOL_NAMESPACE HWMON_NCT6775 + +#define USE_ALTERNATE + +/* used to set data->name = nct6775_device_names[data->sio_kind] */ +static const char * const nct6775_device_names[] = { + "nct6106", + "nct6116", + "nct6775", + "nct6776", + "nct6779", + "nct6791", + "nct6792", + "nct6793", + "nct6795", + "nct6796", + "nct6797", + "nct6798", +}; + +/* 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 +#define NCT6775_REG_DIODE 0x5E +#define NCT6775_DIODE_MASK 0x02 + +static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; + +/* 0..15 voltages, 16..23 fans, 24..29 temperatures, 30..31 intrusion */ + +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, -1, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, -1 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6775_REG_BEEP[NUM_REG_BEEP] = { 0x56, 0x57, 0x453, 0x4e }; + +/* + * 0..14 voltages, 15 global beep enable, 16..23 fans, 24..29 temperatures, + * 30..31 intrusion + */ +static const s8 NCT6775_BEEP_BITS[] = { + 0, 1, 2, 3, 8, 9, 10, 16, /* in0.. in7 */ + 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + 21, /* global beep enable */ + 6, 7, 11, 28, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, -1 }; /* intrusion0, intrusion1 */ + +/* DC or PWM output fan configuration */ +static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; +static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; + +/* Advanced Fan control, some values are common for all fans */ + +static const u16 NCT6775_REG_TARGET[] = { + 0x101, 0x201, 0x301, 0x801, 0x901, 0xa01, 0xb01 }; +static const u16 NCT6775_REG_FAN_MODE[] = { + 0x102, 0x202, 0x302, 0x802, 0x902, 0xa02, 0xb02 }; +static const u16 NCT6775_REG_FAN_STEP_DOWN_TIME[] = { + 0x103, 0x203, 0x303, 0x803, 0x903, 0xa03, 0xb03 }; +static const u16 NCT6775_REG_FAN_STEP_UP_TIME[] = { + 0x104, 0x204, 0x304, 0x804, 0x904, 0xa04, 0xb04 }; +static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { + 0x105, 0x205, 0x305, 0x805, 0x905, 0xa05, 0xb05 }; +static const u16 NCT6775_REG_FAN_START_OUTPUT[] = { + 0x106, 0x206, 0x306, 0x806, 0x906, 0xa06, 0xb06 }; +static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; +static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; + +static const u16 NCT6775_REG_FAN_STOP_TIME[] = { + 0x107, 0x207, 0x307, 0x807, 0x907, 0xa07, 0xb07 }; +static const u16 NCT6775_REG_PWM[] = { + 0x109, 0x209, 0x309, 0x809, 0x909, 0xa09, 0xb09 }; +static const u16 NCT6775_REG_PWM_READ[] = { + 0x01, 0x03, 0x11, 0x13, 0x15, 0xa09, 0xb09 }; + +static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; +static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d }; +static const u16 NCT6775_REG_FAN_PULSES[NUM_FAN] = { + 0x641, 0x642, 0x643, 0x644 }; +static const u16 NCT6775_FAN_PULSE_SHIFT[NUM_FAN] = { }; + +static const u16 NCT6775_REG_TEMP[] = { + 0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d }; + +static const u16 NCT6775_REG_TEMP_MON[] = { 0x73, 0x75, 0x77 }; + +static const u16 NCT6775_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0, 0x152, 0x252, 0x628, 0x629, 0x62A }; +static const u16 NCT6775_REG_TEMP_HYST[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x3a, 0x153, 0x253, 0x673, 0x678, 0x67D }; +static const u16 NCT6775_REG_TEMP_OVER[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x39, 0x155, 0x255, 0x672, 0x677, 0x67C }; + +static const u16 NCT6775_REG_TEMP_SOURCE[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x621, 0x622, 0x623, 0x624, 0x625, 0x626 }; + +static const u16 NCT6775_REG_TEMP_SEL[] = { + 0x100, 0x200, 0x300, 0x800, 0x900, 0xa00, 0xb00 }; + +static const u16 NCT6775_REG_WEIGHT_TEMP_SEL[] = { + 0x139, 0x239, 0x339, 0x839, 0x939, 0xa39 }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP[] = { + 0x13a, 0x23a, 0x33a, 0x83a, 0x93a, 0xa3a }; +static const u16 NCT6775_REG_WEIGHT_TEMP_STEP_TOL[] = { + 0x13b, 0x23b, 0x33b, 0x83b, 0x93b, 0xa3b }; +static const u16 NCT6775_REG_WEIGHT_DUTY_STEP[] = { + 0x13c, 0x23c, 0x33c, 0x83c, 0x93c, 0xa3c }; +static const u16 NCT6775_REG_WEIGHT_TEMP_BASE[] = { + 0x13d, 0x23d, 0x33d, 0x83d, 0x93d, 0xa3d }; + +static const u16 NCT6775_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; + +static const u16 NCT6775_REG_AUTO_TEMP[] = { + 0x121, 0x221, 0x321, 0x821, 0x921, 0xa21, 0xb21 }; +static const u16 NCT6775_REG_AUTO_PWM[] = { + 0x127, 0x227, 0x327, 0x827, 0x927, 0xa27, 0xb27 }; + +#define NCT6775_AUTO_TEMP(data, nr, p) ((data)->REG_AUTO_TEMP[nr] + (p)) +#define NCT6775_AUTO_PWM(data, nr, p) ((data)->REG_AUTO_PWM[nr] + (p)) + +static const u16 NCT6775_REG_CRITICAL_ENAB[] = { 0x134, 0x234, 0x334 }; + +static const u16 NCT6775_REG_CRITICAL_TEMP[] = { + 0x135, 0x235, 0x335, 0x835, 0x935, 0xa35, 0xb35 }; +static const u16 NCT6775_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x138, 0x238, 0x338, 0x838, 0x938, 0xa38, 0xb38 }; + +static const char *const nct6775_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "AMD SB-TSI", + "PECI Agent 0", + "PECI Agent 1", + "PECI Agent 2", + "PECI Agent 3", + "PECI Agent 4", + "PECI Agent 5", + "PECI Agent 6", + "PECI Agent 7", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP" +}; + +#define NCT6775_TEMP_MASK 0x001ffffe +#define NCT6775_VIRT_TEMP_MASK 0x00000000 + +static const u16 NCT6775_REG_TEMP_ALTERNATE[32] = { + [13] = 0x661, + [14] = 0x662, + [15] = 0x664, +}; + +static const u16 NCT6775_REG_TEMP_CRIT[32] = { + [4] = 0xa00, + [5] = 0xa01, + [6] = 0xa02, + [7] = 0xa03, + [8] = 0xa04, + [9] = 0xa05, + [10] = 0xa06, + [11] = 0xa07 +}; + +static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 }; + +/* NCT6776 specific data */ + +/* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */ +#define NCT6776_REG_FAN_STEP_UP_TIME NCT6775_REG_FAN_STEP_DOWN_TIME +#define NCT6776_REG_FAN_STEP_DOWN_TIME NCT6775_REG_FAN_STEP_UP_TIME + +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 */ + +static const u16 NCT6776_REG_BEEP[NUM_REG_BEEP] = { 0xb2, 0xb3, 0xb4, 0xb5 }; + +static const s8 NCT6776_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ + 8, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + 24, /* global beep enable */ + 25, 26, 27, 28, 29, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 30, 31 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6776_REG_TOLERANCE_H[] = { + 0x10c, 0x20c, 0x30c, 0x80c, 0x90c, 0xa0c, 0xb0c }; + +static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0, 0, 0, 0 }; +static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0, 0, 0, 0 }; + +static const u16 NCT6776_REG_FAN_MIN[] = { + 0x63a, 0x63c, 0x63e, 0x640, 0x642, 0x64a, 0x64c }; +static const u16 NCT6776_REG_FAN_PULSES[NUM_FAN] = { + 0x644, 0x645, 0x646, 0x647, 0x648, 0x649 }; + +static const u16 NCT6776_REG_WEIGHT_DUTY_BASE[] = { + 0x13e, 0x23e, 0x33e, 0x83e, 0x93e, 0xa3e }; + +static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { + 0x18, 0x152, 0x252, 0x628, 0x629, 0x62A }; + +static const char *const nct6776_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP" +}; + +#define NCT6776_TEMP_MASK 0x007ffffe +#define NCT6776_VIRT_TEMP_MASK 0x00000000 + +static const u16 NCT6776_REG_TEMP_ALTERNATE[32] = { + [14] = 0x401, + [15] = 0x402, + [16] = 0x404, +}; + +static const u16 NCT6776_REG_TEMP_CRIT[32] = { + [11] = 0x709, + [12] = 0x70a, +}; + +static const u16 NCT6776_REG_TSI_TEMP[] = { + 0x409, 0x40b, 0x40d, 0x40f, 0x411, 0x413, 0x415, 0x417 }; + +/* 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 */ + +static const s8 NCT6779_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ + 8, 9, 10, 11, 12, 13, 14, /* in8..in14 */ + 24, /* global beep enable */ + 25, 26, 27, 28, 29, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, -1, -1, -1, -1, /* temp1..temp6 */ + 30, 31 }; /* intrusion0, intrusion1 */ + +static const u16 NCT6779_REG_FAN[] = { + 0x4c0, 0x4c2, 0x4c4, 0x4c6, 0x4c8, 0x4ca, 0x4ce }; +static const u16 NCT6779_REG_FAN_PULSES[NUM_FAN] = { + 0x644, 0x645, 0x646, 0x647, 0x648, 0x649, 0x64f }; + +static const u16 NCT6779_REG_CRITICAL_PWM_ENABLE[] = { + 0x136, 0x236, 0x336, 0x836, 0x936, 0xa36, 0xb36 }; +#define NCT6779_CRITICAL_PWM_ENABLE_MASK 0x01 +static const u16 NCT6779_REG_CRITICAL_PWM[] = { + 0x137, 0x237, 0x337, 0x837, 0x937, 0xa37, 0xb37 }; + +static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 }; +static const u16 NCT6779_REG_TEMP_MON[] = { 0x73, 0x75, 0x77, 0x79, 0x7b }; +static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x18, 0x152 }; +static const u16 NCT6779_REG_TEMP_HYST[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x3a, 0x153 }; +static const u16 NCT6779_REG_TEMP_OVER[ARRAY_SIZE(NCT6779_REG_TEMP)] = { + 0x39, 0x155 }; + +static const u16 NCT6779_REG_TEMP_OFFSET[] = { + 0x454, 0x455, 0x456, 0x44a, 0x44b, 0x44c }; + +static const char *const nct6779_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP", + "", + "", + "", + "", + "Virtual_TEMP" +}; + +#define NCT6779_TEMP_MASK 0x07ffff7e +#define NCT6779_VIRT_TEMP_MASK 0x00000000 +#define NCT6791_TEMP_MASK 0x87ffff7e +#define NCT6791_VIRT_TEMP_MASK 0x80000000 + +static const u16 NCT6779_REG_TEMP_ALTERNATE[32] + = { 0x490, 0x491, 0x492, 0x493, 0x494, 0x495, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x400, 0x401, 0x402, 0x404, 0x405, 0x406, 0x407, + 0x408, 0 }; + +static const u16 NCT6779_REG_TEMP_CRIT[32] = { + [15] = 0x709, + [16] = 0x70a, +}; + +/* NCT6791 specific data */ + +static const u16 NCT6791_REG_WEIGHT_TEMP_SEL[NUM_FAN] = { 0, 0x239 }; +static const u16 NCT6791_REG_WEIGHT_TEMP_STEP[NUM_FAN] = { 0, 0x23a }; +static const u16 NCT6791_REG_WEIGHT_TEMP_STEP_TOL[NUM_FAN] = { 0, 0x23b }; +static const u16 NCT6791_REG_WEIGHT_DUTY_STEP[NUM_FAN] = { 0, 0x23c }; +static const u16 NCT6791_REG_WEIGHT_TEMP_BASE[NUM_FAN] = { 0, 0x23d }; +static const u16 NCT6791_REG_WEIGHT_DUTY_BASE[NUM_FAN] = { 0, 0x23e }; + +static const u16 NCT6791_REG_ALARM[NUM_REG_ALARM] = { + 0x459, 0x45A, 0x45B, 0x568, 0x45D }; + +static const s8 NCT6791_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, 33, /* fan1..fan6 */ + -1, -1, /* unused */ + 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ + 12, 9 }; /* intrusion0, intrusion1 */ + +/* NCT6792/NCT6793 specific data */ + +static const u16 NCT6792_REG_TEMP_MON[] = { + 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d }; +static const u16 NCT6792_REG_BEEP[NUM_REG_BEEP] = { + 0xb2, 0xb3, 0xb4, 0xb5, 0xbf }; + +static const char *const nct6792_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "PCH_DIM0_TEMP", + "PCH_DIM1_TEMP", + "PCH_DIM2_TEMP", + "PCH_DIM3_TEMP", + "BYTE_TEMP", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "", + "Virtual_TEMP" +}; + +#define NCT6792_TEMP_MASK 0x9fffff7e +#define NCT6792_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6793_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "", + "", + "", + "", + "", + "", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0 ", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6793_TEMP_MASK 0xbfff037e +#define NCT6793_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6795_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "SMBUSMASTER 2", + "SMBUSMASTER 3", + "SMBUSMASTER 4", + "SMBUSMASTER 5", + "SMBUSMASTER 6", + "SMBUSMASTER 7", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6795_TEMP_MASK 0xbfffff7e +#define NCT6795_VIRT_TEMP_MASK 0x80000000 + +static const char *const nct6796_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "AUXTIN4", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "Virtual_TEMP", + "Virtual_TEMP", + "", + "", + "", + "", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", + "PECI Agent 1 Calibration", + "", + "Virtual_TEMP" +}; + +#define NCT6796_TEMP_MASK 0xbfff0ffe +#define NCT6796_VIRT_TEMP_MASK 0x80000c00 + +static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b }; + +static const char *const nct6798_temp_label[] = { + "", + "SYSTIN", + "CPUTIN", + "AUXTIN0", + "AUXTIN1", + "AUXTIN2", + "AUXTIN3", + "AUXTIN4", + "SMBUSMASTER 0", + "SMBUSMASTER 1", + "Virtual_TEMP", + "Virtual_TEMP", + "", + "", + "", + "", + "PECI Agent 0", + "PECI Agent 1", + "PCH_CHIP_CPU_MAX_TEMP", + "PCH_CHIP_TEMP", + "PCH_CPU_TEMP", + "PCH_MCH_TEMP", + "Agent0 Dimm0", + "Agent0 Dimm1", + "Agent1 Dimm0", + "Agent1 Dimm1", + "BYTE_TEMP0", + "BYTE_TEMP1", + "PECI Agent 0 Calibration", /* undocumented */ + "PECI Agent 1 Calibration", /* undocumented */ + "", + "Virtual_TEMP" +}; + +#define NCT6798_TEMP_MASK 0xbfff0ffe +#define NCT6798_VIRT_TEMP_MASK 0x80000c00 + +/* NCT6102D/NCT6106D specific data */ + +#define NCT6106_REG_VBAT 0x318 +#define NCT6106_REG_DIODE 0x319 +#define NCT6106_DIODE_MASK 0x01 + +static const u16 NCT6106_REG_IN_MAX[] = { + 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9e, 0xa0, 0xa2 }; +static const u16 NCT6106_REG_IN_MIN[] = { + 0x91, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9f, 0xa1, 0xa3 }; +static const u16 NCT6106_REG_IN[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09 }; + +static const u16 NCT6106_REG_TEMP[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; +static const u16 NCT6106_REG_TEMP_MON[] = { 0x18, 0x19, 0x1a }; +static const u16 NCT6106_REG_TEMP_HYST[] = { + 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7 }; +static const u16 NCT6106_REG_TEMP_OVER[] = { + 0xc2, 0xc6, 0xca, 0xce, 0xd2, 0xd6 }; +static const u16 NCT6106_REG_TEMP_CRIT_L[] = { + 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4 }; +static const u16 NCT6106_REG_TEMP_CRIT_H[] = { + 0xc1, 0xc5, 0xc9, 0xcf, 0xd1, 0xd5 }; +static const u16 NCT6106_REG_TEMP_OFFSET[] = { 0x311, 0x312, 0x313 }; +static const u16 NCT6106_REG_TEMP_CONFIG[] = { + 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc }; + +static const u16 NCT6106_REG_FAN[] = { 0x20, 0x22, 0x24 }; +static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 }; +static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 }; +static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 }; + +static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 }; +static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 }; +static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c }; +static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 }; +static const u16 NCT6106_REG_TEMP_SOURCE[] = { + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 }; + +static const u16 NCT6106_REG_CRITICAL_TEMP[] = { 0x11a, 0x12a, 0x13a }; +static const u16 NCT6106_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x11b, 0x12b, 0x13b }; + +static const u16 NCT6106_REG_CRITICAL_PWM_ENABLE[] = { 0x11c, 0x12c, 0x13c }; +#define NCT6106_CRITICAL_PWM_ENABLE_MASK 0x10 +static const u16 NCT6106_REG_CRITICAL_PWM[] = { 0x11d, 0x12d, 0x13d }; + +static const u16 NCT6106_REG_FAN_STEP_UP_TIME[] = { 0x114, 0x124, 0x134 }; +static const u16 NCT6106_REG_FAN_STEP_DOWN_TIME[] = { 0x115, 0x125, 0x135 }; +static const u16 NCT6106_REG_FAN_STOP_OUTPUT[] = { 0x116, 0x126, 0x136 }; +static const u16 NCT6106_REG_FAN_START_OUTPUT[] = { 0x117, 0x127, 0x137 }; +static const u16 NCT6106_REG_FAN_STOP_TIME[] = { 0x118, 0x128, 0x138 }; +static const u16 NCT6106_REG_TOLERANCE_H[] = { 0x112, 0x122, 0x132 }; + +static const u16 NCT6106_REG_TARGET[] = { 0x111, 0x121, 0x131 }; + +static const u16 NCT6106_REG_WEIGHT_TEMP_SEL[] = { 0x168, 0x178, 0x188 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP[] = { 0x169, 0x179, 0x189 }; +static const u16 NCT6106_REG_WEIGHT_TEMP_STEP_TOL[] = { 0x16a, 0x17a, 0x18a }; +static const u16 NCT6106_REG_WEIGHT_DUTY_STEP[] = { 0x16b, 0x17b, 0x18b }; +static const u16 NCT6106_REG_WEIGHT_TEMP_BASE[] = { 0x16c, 0x17c, 0x18c }; +static const u16 NCT6106_REG_WEIGHT_DUTY_BASE[] = { 0x16d, 0x17d, 0x18d }; + +static const u16 NCT6106_REG_AUTO_TEMP[] = { 0x160, 0x170, 0x180 }; +static const u16 NCT6106_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184 }; + +static const u16 NCT6106_REG_ALARM[NUM_REG_ALARM] = { + 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d }; + +static const s8 NCT6106_ALARM_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, -1, -1, -1, -1, -1, -1, /* in8..in14 */ + -1, /* unused */ + 32, 33, 34, -1, -1, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 48, -1 /* intrusion0, intrusion1 */ +}; + +static const u16 NCT6106_REG_BEEP[NUM_REG_BEEP] = { + 0x3c0, 0x3c1, 0x3c2, 0x3c3, 0x3c4 }; + +static const s8 NCT6106_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ + 32, /* global beep enable */ + 24, 25, 26, 27, 28, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ + 34, -1 /* intrusion0, intrusion1 */ +}; + +static const u16 NCT6106_REG_TEMP_ALTERNATE[32] = { + [14] = 0x51, + [15] = 0x52, + [16] = 0x54, +}; + +static const u16 NCT6106_REG_TEMP_CRIT[32] = { + [11] = 0x204, + [12] = 0x205, +}; + +static const u16 NCT6106_REG_TSI_TEMP[] = { 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65, 0x67 }; + +/* NCT6112D/NCT6114D/NCT6116D specific data */ + +static const u16 NCT6116_REG_FAN[] = { 0x20, 0x22, 0x24, 0x26, 0x28 }; +static const u16 NCT6116_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4, 0xe6, 0xe8 }; +static const u16 NCT6116_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6, 0xf6, 0xf5 }; +static const u16 NCT6116_FAN_PULSE_SHIFT[] = { 0, 2, 4, 6, 6 }; + +static const u16 NCT6116_REG_PWM[] = { 0x119, 0x129, 0x139, 0x199, 0x1a9 }; +static const u16 NCT6116_REG_FAN_MODE[] = { 0x113, 0x123, 0x133, 0x193, 0x1a3 }; +static const u16 NCT6116_REG_TEMP_SEL[] = { 0x110, 0x120, 0x130, 0x190, 0x1a0 }; +static const u16 NCT6116_REG_TEMP_SOURCE[] = { + 0xb0, 0xb1, 0xb2 }; + +static const u16 NCT6116_REG_CRITICAL_TEMP[] = { + 0x11a, 0x12a, 0x13a, 0x19a, 0x1aa }; +static const u16 NCT6116_REG_CRITICAL_TEMP_TOLERANCE[] = { + 0x11b, 0x12b, 0x13b, 0x19b, 0x1ab }; + +static const u16 NCT6116_REG_CRITICAL_PWM_ENABLE[] = { + 0x11c, 0x12c, 0x13c, 0x19c, 0x1ac }; +static const u16 NCT6116_REG_CRITICAL_PWM[] = { + 0x11d, 0x12d, 0x13d, 0x19d, 0x1ad }; + +static const u16 NCT6116_REG_FAN_STEP_UP_TIME[] = { + 0x114, 0x124, 0x134, 0x194, 0x1a4 }; +static const u16 NCT6116_REG_FAN_STEP_DOWN_TIME[] = { + 0x115, 0x125, 0x135, 0x195, 0x1a5 }; +static const u16 NCT6116_REG_FAN_STOP_OUTPUT[] = { + 0x116, 0x126, 0x136, 0x196, 0x1a6 }; +static const u16 NCT6116_REG_FAN_START_OUTPUT[] = { + 0x117, 0x127, 0x137, 0x197, 0x1a7 }; +static const u16 NCT6116_REG_FAN_STOP_TIME[] = { + 0x118, 0x128, 0x138, 0x198, 0x1a8 }; +static const u16 NCT6116_REG_TOLERANCE_H[] = { + 0x112, 0x122, 0x132, 0x192, 0x1a2 }; + +static const u16 NCT6116_REG_TARGET[] = { + 0x111, 0x121, 0x131, 0x191, 0x1a1 }; + +static const u16 NCT6116_REG_AUTO_TEMP[] = { + 0x160, 0x170, 0x180, 0x1d0, 0x1e0 }; +static const u16 NCT6116_REG_AUTO_PWM[] = { + 0x164, 0x174, 0x184, 0x1d4, 0x1e4 }; + +static const s8 NCT6116_ALARM_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, -1, -1, -1, -1, -1, -1, /* in8..in9 */ + -1, /* unused */ + 32, 33, 34, 35, 36, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ + 48, -1 /* intrusion0, intrusion1 */ +}; + +static const s8 NCT6116_BEEP_BITS[] = { + 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ + 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ + 32, /* global beep enable */ + 24, 25, 26, 27, 28, /* fan1..fan5 */ + -1, -1, -1, /* unused */ + 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ + 34, -1 /* intrusion0, intrusion1 */ +}; + +static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; + +static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) +{ + if (mode == 0 && pwm == 255) + return off; + return mode + 1; +} + +static int pwm_enable_to_reg(enum pwm_enable mode) +{ + if (mode == off) + return 0; + return mode - 1; +} + +/* + * Conversions + */ + +/* 1 is DC mode, output in ms */ +static unsigned int step_time_from_reg(u8 reg, u8 mode) +{ + return mode ? 400 * reg : 100 * reg; +} + +static u8 step_time_to_reg(unsigned int msec, u8 mode) +{ + return clamp_val((mode ? (msec + 200) / 400 : + (msec + 50) / 100), 1, 255); +} + +static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 255) + return 0; + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) +{ + if ((reg & 0xff1f) == 0xff1f) + return 0; + + reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); + + if (reg == 0) + return 0; + + return 1350000U / reg; +} + +static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) +{ + if (reg == 0 || reg == 0xffff) + return 0; + + /* + * Even though the registers are 16 bit wide, the fan divisor + * still applies. + */ + return 1350000U / (reg << divreg); +} + +static unsigned int fan_from_reg_rpm(u16 reg, unsigned int divreg) +{ + return reg; +} + +static u16 fan_to_reg(u32 fan, unsigned int divreg) +{ + if (!fan) + return 0; + + return (1350000U / fan) >> divreg; +} + +static inline unsigned int +div_from_reg(u8 reg) +{ + return BIT(reg); +} + +/* + * 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); +} + +/* TSI temperatures are in 8.3 format */ +static inline unsigned int tsi_temp_from_reg(unsigned int reg) +{ + return (reg >> 5) * 125; +} + +/* + * Data structures and manipulation thereof + */ + +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 int nct6775_add_template_attr_group(struct device *dev, struct nct6775_data *data, + const struct sensor_template_group *tg, int repeat) +{ + struct attribute_group *group; + struct sensor_device_attr_u *su; + struct sensor_device_attribute *a; + struct sensor_device_attribute_2 *a2; + struct attribute **attrs; + struct sensor_device_template **t; + int i, count; + + if (repeat <= 0) + return -EINVAL; + + t = tg->templates; + for (count = 0; *t; t++, count++) + ; + + if (count == 0) + return -EINVAL; + + group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); + if (group == NULL) + return -ENOMEM; + + attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), + GFP_KERNEL); + if (attrs == NULL) + return -ENOMEM; + + su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), + GFP_KERNEL); + if (su == NULL) + return -ENOMEM; + + group->attrs = attrs; + group->is_visible = tg->is_visible; + + for (i = 0; i < repeat; i++) { + t = tg->templates; + while (*t != NULL) { + 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 nct6775_add_attr_group(data, group); +} + +bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg) +{ + switch (data->kind) { + case nct6106: + return reg == 0x20 || reg == 0x22 || reg == 0x24 || + (reg >= 0x59 && reg < 0x69 && (reg & 1)) || + reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || + reg == 0x111 || reg == 0x121 || reg == 0x131; + case nct6116: + return reg == 0x20 || reg == 0x22 || reg == 0x24 || + reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b || + reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 || + reg == 0xe8 || reg == 0x111 || reg == 0x121 || reg == 0x131 || + reg == 0x191 || reg == 0x1a1; + 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 == 0x669 || + ((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 >= 0x409 && reg < 0x419 && (reg & 1)) || + reg == 0x640 || reg == 0x642 || + ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || + reg == 0x73 || reg == 0x75 || reg == 0x77; + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + return reg == 0x150 || reg == 0x153 || reg == 0x155 || + (reg & 0xfff0) == 0x4c0 || + reg == 0x402 || + (reg >= 0x409 && reg < 0x419 && (reg & 1)) || + reg == 0x63a || reg == 0x63c || reg == 0x63e || + reg == 0x640 || reg == 0x642 || reg == 0x64a || + reg == 0x64c || + reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 || + reg == 0x7b || reg == 0x7d; + } + return false; +} +EXPORT_SYMBOL_GPL(nct6775_reg_is_word_sized); + +/* We left-align 8-bit temperature values to make the code simpler */ +static int nct6775_read_temp(struct nct6775_data *data, u16 reg, u16 *val) +{ + int err; + + err = nct6775_read_value(data, reg, val); + if (err) + return err; + + if (!nct6775_reg_is_word_sized(data, reg)) + *val <<= 8; + + return 0; +} + +/* This function assumes that the caller holds data->update_lock */ +static int nct6775_write_fan_div(struct nct6775_data *data, int nr) +{ + u16 reg; + int err; + u16 fandiv_reg = nr < 2 ? NCT6775_REG_FANDIV1 : NCT6775_REG_FANDIV2; + unsigned int oddshift = (nr & 1) * 4; /* masks shift by four if nr is odd */ + + err = nct6775_read_value(data, fandiv_reg, ®); + if (err) + return err; + reg &= 0x70 >> oddshift; + reg |= data->fan_div[nr] & (0x7 << oddshift); + return nct6775_write_value(data, fandiv_reg, reg); +} + +static int nct6775_write_fan_div_common(struct nct6775_data *data, int nr) +{ + if (data->kind == nct6775) + return nct6775_write_fan_div(data, nr); + return 0; +} + +static int nct6775_update_fan_div(struct nct6775_data *data) +{ + int err; + u16 i; + + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &i); + if (err) + return err; + data->fan_div[0] = i & 0x7; + data->fan_div[1] = (i & 0x70) >> 4; + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &i); + if (err) + return err; + data->fan_div[2] = i & 0x7; + if (data->has_fan & BIT(3)) + data->fan_div[3] = (i & 0x70) >> 4; + + return 0; +} + +static int nct6775_update_fan_div_common(struct nct6775_data *data) +{ + if (data->kind == nct6775) + return nct6775_update_fan_div(data); + return 0; +} + +static int nct6775_init_fan_div(struct nct6775_data *data) +{ + int i, err; + + err = nct6775_update_fan_div_common(data); + if (err) + return err; + + /* + * For all fans, start with highest divider value if the divider + * register is not initialized. This ensures that we get a + * reading from the fan count register, even if it is not optimal. + * We'll compute a better divider later on. + */ + for (i = 0; i < ARRAY_SIZE(data->fan_div); i++) { + if (!(data->has_fan & BIT(i))) + continue; + if (data->fan_div[i] == 0) { + data->fan_div[i] = 7; + err = nct6775_write_fan_div_common(data, i); + if (err) + return err; + } + } + + return 0; +} + +static int nct6775_init_fan_common(struct device *dev, + struct nct6775_data *data) +{ + int i, err; + u16 reg; + + if (data->has_fan_div) { + err = nct6775_init_fan_div(data); + if (err) + return err; + } + + /* + * If fan_min is not set (0), set it to 0xff to disable it. This + * prevents the unnecessary warning when fanX_min is reported as 0. + */ + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + if (data->has_fan_min & BIT(i)) { + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + return err; + if (!reg) { + err = nct6775_write_value(data, data->REG_FAN_MIN[i], + data->has_fan_div ? 0xff : 0xff1f); + if (err) + return err; + } + } + } + + return 0; +} + +static int nct6775_select_fan_div(struct device *dev, + struct nct6775_data *data, int nr, u16 reg) +{ + int err; + u8 fan_div = data->fan_div[nr]; + u16 fan_min; + + if (!data->has_fan_div) + return 0; + + /* + * If we failed to measure the fan speed, or the reported value is not + * in the optimal range, and the clock divider can be modified, + * let's try that for next time. + */ + if (reg == 0x00 && fan_div < 0x07) + fan_div++; + else if (reg != 0x00 && reg < 0x30 && fan_div > 0) + fan_div--; + + if (fan_div != data->fan_div[nr]) { + dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(fan_div)); + + /* Preserve min limit if possible */ + if (data->has_fan_min & BIT(nr)) { + fan_min = data->fan_min[nr]; + if (fan_div > data->fan_div[nr]) { + if (fan_min != 255 && fan_min > 1) + fan_min >>= 1; + } else { + if (fan_min != 255) { + fan_min <<= 1; + if (fan_min > 254) + fan_min = 254; + } + } + if (fan_min != data->fan_min[nr]) { + data->fan_min[nr] = fan_min; + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], fan_min); + if (err) + return err; + } + } + data->fan_div[nr] = fan_div; + err = nct6775_write_fan_div_common(data, nr); + if (err) + return err; + } + + return 0; +} + +static int nct6775_update_pwm(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j, err; + u16 fanmodecfg, reg; + bool duty_is_dc; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & BIT(i))) + continue; + + err = nct6775_read_value(data, data->REG_PWM_MODE[i], ®); + if (err) + return err; + duty_is_dc = data->REG_PWM_MODE[i] && (reg & data->PWM_MODE_MASK[i]); + data->pwm_mode[i] = !duty_is_dc; + + err = nct6775_read_value(data, data->REG_FAN_MODE[i], &fanmodecfg); + if (err) + return err; + for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { + if (data->REG_PWM[j] && data->REG_PWM[j][i]) { + err = nct6775_read_value(data, data->REG_PWM[j][i], ®); + if (err) + return err; + data->pwm[j][i] = reg; + } + } + + data->pwm_enable[i] = reg_to_pwm_enable(data->pwm[0][i], + (fanmodecfg >> 4) & 7); + + if (!data->temp_tolerance[0][i] || + data->pwm_enable[i] != speed_cruise) + data->temp_tolerance[0][i] = fanmodecfg & 0x0f; + if (!data->target_speed_tolerance[i] || + data->pwm_enable[i] == speed_cruise) { + u8 t = fanmodecfg & 0x0f; + + if (data->REG_TOLERANCE_H) { + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + t |= (reg & 0x70) >> 1; + } + data->target_speed_tolerance[i] = t; + } + + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[i], ®); + if (err) + return err; + data->temp_tolerance[1][i] = reg; + + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], ®); + if (err) + return err; + data->pwm_temp_sel[i] = reg & 0x1f; + /* If fan can stop, report floor as 0 */ + if (reg & 0x80) + data->pwm[2][i] = 0; + + if (!data->REG_WEIGHT_TEMP_SEL[i]) + continue; + + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i], ®); + if (err) + return err; + data->pwm_weight_temp_sel[i] = reg & 0x1f; + /* If weight is disabled, report weight source as 0 */ + if (!(reg & 0x80)) + data->pwm_weight_temp_sel[i] = 0; + + /* Weight temp data */ + for (j = 0; j < ARRAY_SIZE(data->weight_temp); j++) { + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP[j][i], ®); + if (err) + return err; + data->weight_temp[j][i] = reg; + } + } + + return 0; +} + +static int nct6775_update_pwm_limits(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j, err; + u16 reg, reg_t; + + for (i = 0; i < data->pwm_num; i++) { + if (!(data->has_pwm & BIT(i))) + continue; + + for (j = 0; j < ARRAY_SIZE(data->fan_time); j++) { + err = nct6775_read_value(data, data->REG_FAN_TIME[j][i], ®); + if (err) + return err; + data->fan_time[j][i] = reg; + } + + err = nct6775_read_value(data, data->REG_TARGET[i], ®_t); + if (err) + return err; + + /* Update only in matching mode or if never updated */ + if (!data->target_temp[i] || + data->pwm_enable[i] == thermal_cruise) + data->target_temp[i] = reg_t & data->target_temp_mask; + if (!data->target_speed[i] || + data->pwm_enable[i] == speed_cruise) { + if (data->REG_TOLERANCE_H) { + err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); + if (err) + return err; + reg_t |= (reg & 0x0f) << 8; + } + data->target_speed[i] = reg_t; + } + + for (j = 0; j < data->auto_pwm_num; j++) { + err = nct6775_read_value(data, NCT6775_AUTO_PWM(data, i, j), ®); + if (err) + return err; + data->auto_pwm[i][j] = reg; + + err = nct6775_read_value(data, NCT6775_AUTO_TEMP(data, i, j), ®); + if (err) + return err; + data->auto_temp[i][j] = reg; + } + + /* critical auto_pwm temperature data */ + err = nct6775_read_value(data, data->REG_CRITICAL_TEMP[i], ®); + if (err) + return err; + data->auto_temp[i][data->auto_pwm_num] = reg; + + switch (data->kind) { + case nct6775: + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[i], ®); + if (err) + return err; + data->auto_pwm[i][data->auto_pwm_num] = + (reg & 0x02) ? 0xff : 0x00; + break; + case nct6776: + data->auto_pwm[i][data->auto_pwm_num] = 0xff; + break; + case nct6106: + case nct6116: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[i], ®); + if (err) + return err; + if (reg & data->CRITICAL_PWM_ENABLE_MASK) { + err = nct6775_read_value(data, data->REG_CRITICAL_PWM[i], ®); + if (err) + return err; + } else { + reg = 0xff; + } + data->auto_pwm[i][data->auto_pwm_num] = reg; + break; + } + } + + return 0; +} + +static struct nct6775_data *nct6775_update_device(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + int i, j, err = 0; + u16 reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ + HZ / 2) + || !data->valid) { + /* Fan clock dividers */ + err = nct6775_update_fan_div_common(data); + if (err) + goto out; + + /* Measured voltages and limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & BIT(i))) + continue; + + err = nct6775_read_value(data, data->REG_VIN[i], ®); + if (err) + goto out; + data->in[i][0] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[0][i], ®); + if (err) + goto out; + data->in[i][1] = reg; + + err = nct6775_read_value(data, data->REG_IN_MINMAX[1][i], ®); + if (err) + goto out; + data->in[i][2] = reg; + } + + /* Measured fan speeds and limits */ + for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { + if (!(data->has_fan & BIT(i))) + continue; + + err = nct6775_read_value(data, data->REG_FAN[i], ®); + if (err) + goto out; + data->rpm[i] = data->fan_from_reg(reg, + data->fan_div[i]); + + if (data->has_fan_min & BIT(i)) { + err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); + if (err) + goto out; + data->fan_min[i] = reg; + } + + if (data->REG_FAN_PULSES[i]) { + err = nct6775_read_value(data, data->REG_FAN_PULSES[i], ®); + if (err) + goto out; + data->fan_pulses[i] = (reg >> data->FAN_PULSE_SHIFT[i]) & 0x03; + } + + err = nct6775_select_fan_div(dev, data, i, reg); + if (err) + goto out; + } + + err = nct6775_update_pwm(dev); + if (err) + goto out; + + err = nct6775_update_pwm_limits(dev); + if (err) + goto out; + + /* Measured temperatures and limits */ + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + for (j = 0; j < ARRAY_SIZE(data->reg_temp); j++) { + if (data->reg_temp[j][i]) { + err = nct6775_read_temp(data, data->reg_temp[j][i], ®); + if (err) + goto out; + data->temp[j][i] = reg; + } + } + if (i >= NUM_TEMP_FIXED || + !(data->have_temp_fixed & BIT(i))) + continue; + err = nct6775_read_value(data, data->REG_TEMP_OFFSET[i], ®); + if (err) + goto out; + data->temp_offset[i] = reg; + } + + for (i = 0; i < NUM_TSI_TEMP; i++) { + if (!(data->have_tsi_temp & BIT(i))) + continue; + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], ®); + if (err) + goto out; + data->tsi_temp[i] = reg; + } + + data->alarms = 0; + for (i = 0; i < NUM_REG_ALARM; i++) { + u16 alarm; + + if (!data->REG_ALARM[i]) + continue; + err = nct6775_read_value(data, data->REG_ALARM[i], &alarm); + if (err) + goto out; + data->alarms |= ((u64)alarm) << (i << 3); + } + + data->beeps = 0; + for (i = 0; i < NUM_REG_BEEP; i++) { + u16 beep; + + if (!data->REG_BEEP[i]) + continue; + err = nct6775_read_value(data, data->REG_BEEP[i], &beep); + if (err) + goto out; + data->beeps |= ((u64)beep) << (i << 3); + } + + data->last_updated = jiffies; + data->valid = true; + } +out: + mutex_unlock(&data->update_lock); + return err ? ERR_PTR(err) : 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 index = sattr->index; + int nr = sattr->nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + 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 index = sattr->index; + int nr = sattr->nr; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + mutex_lock(&data->update_lock); + data->in[nr][index] = in_to_reg(val, nr); + err = nct6775_write_value(data, data->REG_IN_MINMAX[index - 1][nr], data->in[nr][index]); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +ssize_t +nct6775_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; + + if (IS_ERR(data)) + return PTR_ERR(data); + + nr = data->ALARM_BITS[sattr->index]; + return sprintf(buf, "%u\n", + (unsigned int)((data->alarms >> nr) & 0x01)); +} +EXPORT_SYMBOL_GPL(nct6775_show_alarm); + +static int find_temp_source(struct nct6775_data *data, int index, int count) +{ + int source = data->temp_src[index]; + int nr, err; + + for (nr = 0; nr < count; nr++) { + u16 src; + + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[nr], &src); + if (err) + return err; + if ((src & 0x1f) == source) + return nr; + } + return -ENODEV; +} + +static ssize_t +show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + unsigned int alarm = 0; + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * For temperatures, there is no fixed mapping from registers to alarm + * bits. Alarm bits are determined by the temperature source mapping. + */ + nr = find_temp_source(data, sattr->index, data->num_temp_alarms); + if (nr >= 0) { + int bit = data->ALARM_BITS[nr + TEMP_ALARM_BASE]; + + alarm = (data->alarms >> bit) & 0x01; + } + return sprintf(buf, "%u\n", alarm); +} + +ssize_t +nct6775_show_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + nr = data->BEEP_BITS[sattr->index]; + + return sprintf(buf, "%u\n", + (unsigned int)((data->beeps >> nr) & 0x01)); +} +EXPORT_SYMBOL_GPL(nct6775_show_beep); + +ssize_t +nct6775_store_beep(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 nct6775_data *data = dev_get_drvdata(dev); + int nr = data->BEEP_BITS[sattr->index]; + int regindex = nr >> 3; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val) + data->beeps |= (1ULL << nr); + else + data->beeps &= ~(1ULL << nr); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); + mutex_unlock(&data->update_lock); + return err ? : count; +} +EXPORT_SYMBOL_GPL(nct6775_store_beep); + +static ssize_t +show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + struct nct6775_data *data = nct6775_update_device(dev); + unsigned int beep = 0; + int nr; + + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * For temperatures, there is no fixed mapping from registers to beep + * enable bits. Beep enable bits are determined by the temperature + * source mapping. + */ + nr = find_temp_source(data, sattr->index, data->num_temp_beeps); + if (nr >= 0) { + int bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; + + beep = (data->beeps >> bit) & 0x01; + } + return sprintf(buf, "%u\n", beep); +} + +static ssize_t +store_temp_beep(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 nct6775_data *data = dev_get_drvdata(dev); + int nr, bit, regindex; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 1) + return -EINVAL; + + nr = find_temp_source(data, sattr->index, data->num_temp_beeps); + if (nr < 0) + return nr; + + bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; + regindex = bit >> 3; + + mutex_lock(&data->update_lock); + if (val) + data->beeps |= (1ULL << bit); + else + data->beeps &= ~(1ULL << bit); + err = nct6775_write_value(data, data->REG_BEEP[regindex], + (data->beeps >> (regindex << 3)) & 0xff); + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static umode_t nct6775_in_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int in = index / 5; /* voltage index */ + + if (!(data->have_in & BIT(in))) + return 0; + + return nct6775_attr_mode(data, attr); +} + +SENSOR_TEMPLATE_2(in_input, "in%d_input", 0444, show_in_reg, NULL, 0, 0); +SENSOR_TEMPLATE(in_alarm, "in%d_alarm", 0444, nct6775_show_alarm, NULL, 0); +SENSOR_TEMPLATE(in_beep, "in%d_beep", 0644, nct6775_show_beep, nct6775_store_beep, 0); +SENSOR_TEMPLATE_2(in_min, "in%d_min", 0644, show_in_reg, store_in_reg, 0, 1); +SENSOR_TEMPLATE_2(in_max, "in%d_max", 0644, show_in_reg, store_in_reg, 0, 2); + +/* + * nct6775_in_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 *nct6775_attributes_in_template[] = { + &sensor_dev_template_in_input, + &sensor_dev_template_in_alarm, + &sensor_dev_template_in_beep, + &sensor_dev_template_in_min, + &sensor_dev_template_in_max, + NULL +}; + +static const struct sensor_template_group nct6775_in_template_group = { + .templates = nct6775_attributes_in_template, + .is_visible = nct6775_in_is_visible, +}; + +static ssize_t +show_fan(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->rpm[nr]); +} + +static ssize_t +show_fan_min(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + data->fan_from_reg_min(data->fan_min[nr], + data->fan_div[nr])); +} + +static ssize_t +show_fan_div(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); +} + +static ssize_t +store_fan_min(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + unsigned int reg; + u8 new_div; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + if (!data->has_fan_div) { + /* NCT6776F or NCT6779D; we know this is a 13 bit register */ + if (!val) { + val = 0xff1f; + } else { + if (val > 1350000U) + val = 135000U; + val = 1350000U / val; + val = (val & 0x1f) | ((val << 3) & 0xff00); + } + data->fan_min[nr] = val; + goto write_min; /* Leave fan divider alone */ + } + if (!val) { + /* No min limit, alarm disabled */ + data->fan_min[nr] = 255; + new_div = data->fan_div[nr]; /* No change */ + dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1); + goto write_div; + } + reg = 1350000U / val; + if (reg >= 128 * 255) { + /* + * Speed below this value cannot possibly be represented, + * even with the highest divider (128) + */ + data->fan_min[nr] = 254; + new_div = 7; /* 128 == BIT(7) */ + dev_warn(dev, + "fan%u low limit %lu below minimum %u, set to minimum\n", + nr + 1, val, data->fan_from_reg_min(254, 7)); + } else if (!reg) { + /* + * Speed above this value cannot possibly be represented, + * even with the lowest divider (1) + */ + data->fan_min[nr] = 1; + new_div = 0; /* 1 == BIT(0) */ + dev_warn(dev, + "fan%u low limit %lu above maximum %u, set to maximum\n", + nr + 1, val, data->fan_from_reg_min(1, 0)); + } else { + /* + * Automatically pick the best divider, i.e. the one such + * that the min limit will correspond to a register value + * in the 96..192 range + */ + new_div = 0; + while (reg > 192 && new_div < 7) { + reg >>= 1; + new_div++; + } + data->fan_min[nr] = reg; + } + +write_div: + /* + * Write both the fan clock divider (if it changed) and the new + * fan min (unconditionally) + */ + if (new_div != data->fan_div[nr]) { + dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", + nr + 1, div_from_reg(data->fan_div[nr]), + div_from_reg(new_div)); + data->fan_div[nr] = new_div; + err = nct6775_write_fan_div_common(data, nr); + if (err) + goto write_min; + /* Give the chip time to sample a new speed value */ + data->last_updated = jiffies; + } + +write_min: + err = nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static ssize_t +show_fan_pulses(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 p; + + if (IS_ERR(data)) + return PTR_ERR(data); + + p = data->fan_pulses[sattr->index]; + return sprintf(buf, "%d\n", p ? : 4); +} + +static ssize_t +store_fan_pulses(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 4) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->fan_pulses[nr] = val & 3; + err = nct6775_read_value(data, data->REG_FAN_PULSES[nr], ®); + if (err) + goto out; + reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); + reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; + err = nct6775_write_value(data, data->REG_FAN_PULSES[nr], reg); +out: + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static umode_t nct6775_fan_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int fan = index / 6; /* fan index */ + int nr = index % 6; /* attribute index */ + + if (!(data->has_fan & BIT(fan))) + return 0; + + if (nr == 1 && data->ALARM_BITS[FAN_ALARM_BASE + fan] == -1) + return 0; + if (nr == 2 && data->BEEP_BITS[FAN_ALARM_BASE + fan] == -1) + return 0; + if (nr == 3 && !data->REG_FAN_PULSES[fan]) + return 0; + if (nr == 4 && !(data->has_fan_min & BIT(fan))) + return 0; + if (nr == 5 && data->kind != nct6775) + return 0; + + return nct6775_attr_mode(data, attr); +} + +SENSOR_TEMPLATE(fan_input, "fan%d_input", 0444, show_fan, NULL, 0); +SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", 0444, nct6775_show_alarm, NULL, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_beep, "fan%d_beep", 0644, nct6775_show_beep, + nct6775_store_beep, FAN_ALARM_BASE); +SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", 0644, show_fan_pulses, store_fan_pulses, 0); +SENSOR_TEMPLATE(fan_min, "fan%d_min", 0644, show_fan_min, store_fan_min, 0); +SENSOR_TEMPLATE(fan_div, "fan%d_div", 0444, show_fan_div, NULL, 0); + +/* + * nct6775_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 *nct6775_attributes_fan_template[] = { + &sensor_dev_template_fan_input, + &sensor_dev_template_fan_alarm, /* 1 */ + &sensor_dev_template_fan_beep, /* 2 */ + &sensor_dev_template_fan_pulses, + &sensor_dev_template_fan_min, /* 4 */ + &sensor_dev_template_fan_div, /* 5 */ + NULL +}; + +static const struct sensor_template_group nct6775_fan_template_group = { + .templates = nct6775_attributes_fan_template, + .is_visible = nct6775_fan_is_visible, + .base = 1, +}; + +static ssize_t +show_temp_label(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); +} + +static ssize_t +show_temp(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; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->temp[index][nr])); +} + +static ssize_t +store_temp(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; + int err; + long val; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + mutex_lock(&data->update_lock); + data->temp[index][nr] = LM75_TEMP_TO_REG(val); + err = nct6775_write_temp(data, data->reg_temp[index][nr], data->temp[index][nr]); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_temp_offset(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_offset[sattr->index] * 1000); +} + +static ssize_t +store_temp_offset(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + long val; + int err; + + err = kstrtol(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); + + mutex_lock(&data->update_lock); + data->temp_offset[nr] = val; + err = nct6775_write_value(data, data->REG_TEMP_OFFSET[nr], val); + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static ssize_t +show_temp_type(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", (int)data->temp_type[nr]); +} + +static ssize_t +store_temp_type(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u8 vbit, dbit; + u16 vbat, diode; + + if (IS_ERR(data)) + return PTR_ERR(data); + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val != 1 && val != 3 && val != 4) + return -EINVAL; + + mutex_lock(&data->update_lock); + + data->temp_type[nr] = val; + vbit = 0x02 << nr; + dbit = data->DIODE_MASK << nr; + + err = nct6775_read_value(data, data->REG_VBAT, &vbat); + if (err) + goto out; + vbat &= ~vbit; + + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + goto out; + diode &= ~dbit; + + switch (val) { + case 1: /* CPU diode (diode, current mode) */ + vbat |= vbit; + diode |= dbit; + break; + case 3: /* diode, voltage mode */ + vbat |= dbit; + break; + case 4: /* thermistor */ + break; + } + err = nct6775_write_value(data, data->REG_VBAT, vbat); + if (err) + goto out; + err = nct6775_write_value(data, data->REG_DIODE, diode); +out: + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static umode_t nct6775_temp_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int temp = index / 10; /* temp index */ + int nr = index % 10; /* attribute index */ + + if (!(data->have_temp & BIT(temp))) + return 0; + + if (nr == 1 && !data->temp_label) + return 0; + + if (nr == 2 && find_temp_source(data, temp, data->num_temp_alarms) < 0) + return 0; /* alarm */ + + if (nr == 3 && find_temp_source(data, temp, data->num_temp_beeps) < 0) + return 0; /* beep */ + + if (nr == 4 && !data->reg_temp[1][temp]) /* max */ + return 0; + + if (nr == 5 && !data->reg_temp[2][temp]) /* max_hyst */ + return 0; + + if (nr == 6 && !data->reg_temp[3][temp]) /* crit */ + return 0; + + if (nr == 7 && !data->reg_temp[4][temp]) /* lcrit */ + return 0; + + /* offset and type only apply to fixed sensors */ + if (nr > 7 && !(data->have_temp_fixed & BIT(temp))) + return 0; + + return nct6775_attr_mode(data, attr); +} + +SENSOR_TEMPLATE_2(temp_input, "temp%d_input", 0444, show_temp, NULL, 0, 0); +SENSOR_TEMPLATE(temp_label, "temp%d_label", 0444, show_temp_label, NULL, 0); +SENSOR_TEMPLATE_2(temp_max, "temp%d_max", 0644, show_temp, store_temp, 0, 1); +SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", 0644, show_temp, store_temp, 0, 2); +SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", 0644, show_temp, store_temp, 0, 3); +SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", 0644, show_temp, store_temp, 0, 4); +SENSOR_TEMPLATE(temp_offset, "temp%d_offset", 0644, show_temp_offset, store_temp_offset, 0); +SENSOR_TEMPLATE(temp_type, "temp%d_type", 0644, show_temp_type, store_temp_type, 0); +SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", 0444, show_temp_alarm, NULL, 0); +SENSOR_TEMPLATE(temp_beep, "temp%d_beep", 0644, show_temp_beep, store_temp_beep, 0); + +/* + * nct6775_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 *nct6775_attributes_temp_template[] = { + &sensor_dev_template_temp_input, + &sensor_dev_template_temp_label, + &sensor_dev_template_temp_alarm, /* 2 */ + &sensor_dev_template_temp_beep, /* 3 */ + &sensor_dev_template_temp_max, /* 4 */ + &sensor_dev_template_temp_max_hyst, /* 5 */ + &sensor_dev_template_temp_crit, /* 6 */ + &sensor_dev_template_temp_lcrit, /* 7 */ + &sensor_dev_template_temp_offset, /* 8 */ + &sensor_dev_template_temp_type, /* 9 */ + NULL +}; + +static const struct sensor_template_group nct6775_temp_template_group = { + .templates = nct6775_attributes_temp_template, + .is_visible = nct6775_temp_is_visible, + .base = 1, +}; + +static ssize_t show_tsi_temp(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); +} + +static ssize_t show_tsi_temp_label(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + + return sysfs_emit(buf, "TSI%d_TEMP\n", sattr->index); +} + +SENSOR_TEMPLATE(tsi_temp_input, "temp%d_input", 0444, show_tsi_temp, NULL, 0); +SENSOR_TEMPLATE(tsi_temp_label, "temp%d_label", 0444, show_tsi_temp_label, NULL, 0); + +static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribute *attr, + int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int temp = index / 2; + + return (data->have_tsi_temp & BIT(temp)) ? nct6775_attr_mode(data, attr) : 0; +} + +/* + * The index calculation in nct6775_tsi_temp_is_visible() must be kept in + * sync with the size of this array. + */ +static struct sensor_device_template *nct6775_tsi_temp_template[] = { + &sensor_dev_template_tsi_temp_input, + &sensor_dev_template_tsi_temp_label, + NULL +}; + +static ssize_t +show_pwm_mode(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->pwm_mode[sattr->index]); +} + +static ssize_t +store_pwm_mode(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > 1) + return -EINVAL; + + /* Setting DC mode (0) is not supported for all chips/channels */ + if (data->REG_PWM_MODE[nr] == 0) { + if (!val) + return -EINVAL; + return count; + } + + mutex_lock(&data->update_lock); + data->pwm_mode[nr] = val; + err = nct6775_read_value(data, data->REG_PWM_MODE[nr], ®); + if (err) + goto out; + reg &= ~data->PWM_MODE_MASK[nr]; + if (!val) + reg |= data->PWM_MODE_MASK[nr]; + err = nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); +out: + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_pwm(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; + int err; + u16 pwm; + + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * For automatic fan control modes, show current pwm readings. + * Otherwise, show the configured value. + */ + if (index == 0 && data->pwm_enable[nr] > manual) { + err = nct6775_read_value(data, data->REG_PWM_READ[nr], &pwm); + if (err) + return err; + } else { + pwm = data->pwm[index][nr]; + } + + return sprintf(buf, "%d\n", pwm); +} + +static ssize_t +store_pwm(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 minval[7] = { 0, 1, 1, data->pwm[2][nr], 0, 0, 0 }; + int maxval[7] + = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + val = clamp_val(val, minval[index], maxval[index]); + + mutex_lock(&data->update_lock); + data->pwm[index][nr] = val; + err = nct6775_write_value(data, data->REG_PWM[index][nr], val); + if (err) + goto out; + if (index == 2) { /* floor: disable if val == 0 */ + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; + reg &= 0x7f; + if (val) + reg |= 0x80; + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); + } +out: + mutex_unlock(&data->update_lock); + return err ? : count; +} + +/* Returns 0 if OK, -EINVAL otherwise */ +static int check_trip_points(struct nct6775_data *data, int nr) +{ + int i; + + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) + return -EINVAL; + } + for (i = 0; i < data->auto_pwm_num - 1; i++) { + if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) + return -EINVAL; + } + /* validate critical temperature and pwm if enabled (pwm > 0) */ + if (data->auto_pwm[nr][data->auto_pwm_num]) { + if (data->auto_temp[nr][data->auto_pwm_num - 1] > + data->auto_temp[nr][data->auto_pwm_num] || + data->auto_pwm[nr][data->auto_pwm_num - 1] > + data->auto_pwm[nr][data->auto_pwm_num]) + return -EINVAL; + } + return 0; +} + +static int pwm_update_registers(struct nct6775_data *data, int nr) +{ + u16 reg; + int err; + + switch (data->pwm_enable[nr]) { + case off: + case manual: + break; + case speed_cruise: + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; + reg = (reg & ~data->tolerance_mask) | + (data->target_speed_tolerance[nr] & data->tolerance_mask); + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; + err = nct6775_write_value(data, data->REG_TARGET[nr], + data->target_speed[nr] & 0xff); + if (err) + return err; + if (data->REG_TOLERANCE_H) { + reg = (data->target_speed[nr] >> 8) & 0x0f; + reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; + err = nct6775_write_value(data, data->REG_TOLERANCE_H[nr], reg); + if (err) + return err; + } + break; + case thermal_cruise: + err = nct6775_write_value(data, data->REG_TARGET[nr], data->target_temp[nr]); + if (err) + return err; + fallthrough; + default: + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + return err; + reg = (reg & ~data->tolerance_mask) | + data->temp_tolerance[0][nr]; + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); + if (err) + return err; + break; + } + + return 0; +} + +static ssize_t +show_pwm_enable(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); +} + +static ssize_t +store_pwm_enable(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + if (val > sf4) + return -EINVAL; + + if (val == sf3 && data->kind != nct6775) + return -EINVAL; + + if (val == sf4 && check_trip_points(data, nr)) { + dev_err(dev, "Inconsistent trip points, not switching to SmartFan IV mode\n"); + dev_err(dev, "Adjust trip points and try again\n"); + return -EINVAL; + } + + mutex_lock(&data->update_lock); + data->pwm_enable[nr] = val; + if (val == off) { + /* + * turn off pwm control: select manual mode, set pwm to maximum + */ + data->pwm[0][nr] = 255; + err = nct6775_write_value(data, data->REG_PWM[0][nr], 255); + if (err) + goto out; + } + err = pwm_update_registers(data, nr); + if (err) + goto out; + err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); + if (err) + goto out; + reg &= 0x0f; + reg |= pwm_enable_to_reg(val) << 4; + err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); +out: + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_pwm_temp_sel_common(struct nct6775_data *data, char *buf, int src) +{ + int i, sel = 0; + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + if (src == data->temp_src[i]) { + sel = i + 1; + break; + } + } + + return sprintf(buf, "%d\n", sel); +} + +static ssize_t +show_pwm_temp_sel(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 index = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); +} + +static ssize_t +store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val == 0 || val > NUM_TEMP) + return -EINVAL; + if (!(data->have_temp & BIT(val - 1)) || !data->temp_src[val - 1]) + return -EINVAL; + + mutex_lock(&data->update_lock); + src = data->temp_src[val - 1]; + data->pwm_temp_sel[nr] = src; + err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); + if (err) + goto out; + reg &= 0xe0; + reg |= src; + err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); +out: + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static ssize_t +show_pwm_weight_temp_sel(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 index = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return show_pwm_temp_sel_common(data, buf, + data->pwm_weight_temp_sel[index]); +} + +static ssize_t +store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = nct6775_update_device(dev); + struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err, src; + u16 reg; + + if (IS_ERR(data)) + return PTR_ERR(data); + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > NUM_TEMP) + return -EINVAL; + val = array_index_nospec(val, NUM_TEMP + 1); + if (val && (!(data->have_temp & BIT(val - 1)) || + !data->temp_src[val - 1])) + return -EINVAL; + + mutex_lock(&data->update_lock); + if (val) { + src = data->temp_src[val - 1]; + data->pwm_weight_temp_sel[nr] = src; + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; + reg &= 0xe0; + reg |= (src | 0x80); + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } else { + data->pwm_weight_temp_sel[nr] = 0; + err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); + if (err) + goto out; + reg &= 0x7f; + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); + } +out: + mutex_unlock(&data->update_lock); + + return err ? : count; +} + +static ssize_t +show_target_temp(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); +} + +static ssize_t +store_target_temp(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, + data->target_temp_mask); + + mutex_lock(&data->update_lock); + data->target_temp[nr] = val; + err = pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_target_speed(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 = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + fan_from_reg16(data->target_speed[nr], + data->fan_div[nr])); +} + +static ssize_t +store_target_speed(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + u16 speed; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(val, 0, 1350000U); + speed = fan_to_reg(val, data->fan_div[nr]); + + mutex_lock(&data->update_lock); + data->target_speed[nr] = speed; + err = pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_temp_tolerance(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; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); +} + +static ssize_t +store_temp_tolerance(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; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + /* Limit tolerance as needed */ + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask); + + mutex_lock(&data->update_lock); + data->temp_tolerance[index][nr] = val; + if (index) + err = pwm_update_registers(data, nr); + else + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[nr], val); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +/* + * Fan speed tolerance is a tricky beast, since the associated register is + * a tick counter, but the value is reported and configured as rpm. + * Compute resulting low and high rpm values and report the difference. + * A fan speed tolerance only makes sense if a fan target speed has been + * configured, so only display values other than 0 if that is the case. + */ +static ssize_t +show_speed_tolerance(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 = sattr->index; + int target, tolerance = 0; + + if (IS_ERR(data)) + return PTR_ERR(data); + + target = data->target_speed[nr]; + + if (target) { + int low = target - data->target_speed_tolerance[nr]; + int high = target + data->target_speed_tolerance[nr]; + + if (low <= 0) + low = 1; + if (high > 0xffff) + high = 0xffff; + if (high < low) + high = low; + + tolerance = (fan_from_reg16(low, data->fan_div[nr]) + - fan_from_reg16(high, data->fan_div[nr])) / 2; + } + + return sprintf(buf, "%d\n", tolerance); +} + +static ssize_t +store_speed_tolerance(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 *sattr = to_sensor_dev_attr(attr); + int nr = sattr->index; + unsigned long val; + int err; + int low, high; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + high = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) + val; + low = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) - val; + if (low <= 0) + low = 1; + if (high < low) + high = low; + + val = (fan_to_reg(low, data->fan_div[nr]) - + fan_to_reg(high, data->fan_div[nr])) / 2; + + /* Limit tolerance as needed */ + val = clamp_val(val, 0, data->speed_tolerance_limit); + + mutex_lock(&data->update_lock); + data->target_speed_tolerance[nr] = val; + err = pwm_update_registers(data, nr); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +SENSOR_TEMPLATE_2(pwm, "pwm%d", 0644, show_pwm, store_pwm, 0, 0); +SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", 0644, show_pwm_mode, store_pwm_mode, 0); +SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", 0644, show_pwm_enable, store_pwm_enable, 0); +SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", 0644, show_pwm_temp_sel, store_pwm_temp_sel, 0); +SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", 0644, show_target_temp, store_target_temp, 0); +SENSOR_TEMPLATE(fan_target, "fan%d_target", 0644, show_target_speed, store_target_speed, 0); +SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", 0644, show_speed_tolerance, + store_speed_tolerance, 0); + +/* Smart Fan registers */ + +static ssize_t +show_weight_temp(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; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); +} + +static ssize_t +store_weight_temp(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; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); + + mutex_lock(&data->update_lock); + data->weight_temp[index][nr] = val; + err = nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", 0644, + show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); +SENSOR_TEMPLATE_2(pwm_weight_temp_step, "pwm%d_weight_temp_step", + 0644, show_weight_temp, store_weight_temp, 0, 0); +SENSOR_TEMPLATE_2(pwm_weight_temp_step_tol, "pwm%d_weight_temp_step_tol", + 0644, show_weight_temp, store_weight_temp, 0, 1); +SENSOR_TEMPLATE_2(pwm_weight_temp_step_base, "pwm%d_weight_temp_step_base", + 0644, show_weight_temp, store_weight_temp, 0, 2); +SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", 0644, show_pwm, store_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", 0644, show_pwm, store_pwm, 0, 6); + +static ssize_t +show_fan_time(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; + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", + step_time_from_reg(data->fan_time[index][nr], + data->pwm_mode[nr])); +} + +static ssize_t +store_fan_time(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; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + + val = step_time_to_reg(val, data->pwm_mode[nr]); + mutex_lock(&data->update_lock); + data->fan_time[index][nr] = val; + err = nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_auto_pwm(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); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); +} + +static ssize_t +store_auto_pwm(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 point = sattr->index; + unsigned long val; + int err; + u16 reg; + + err = kstrtoul(buf, 10, &val); + if (err < 0) + return err; + if (val > 255) + return -EINVAL; + + if (point == data->auto_pwm_num) { + if (data->kind != nct6775 && !val) + return -EINVAL; + if (data->kind != nct6779 && val) + val = 0xff; + } + + mutex_lock(&data->update_lock); + data->auto_pwm[nr][point] = val; + if (point < data->auto_pwm_num) { + err = nct6775_write_value(data, NCT6775_AUTO_PWM(data, nr, point), + data->auto_pwm[nr][point]); + } else { + switch (data->kind) { + case nct6775: + /* disable if needed (pwm == 0) */ + err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[nr], ®); + if (err) + break; + if (val) + reg |= 0x02; + else + reg &= ~0x02; + err = nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], reg); + break; + case nct6776: + break; /* always enabled, nothing to do */ + case nct6106: + case nct6116: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + err = nct6775_write_value(data, data->REG_CRITICAL_PWM[nr], val); + if (err) + break; + err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], ®); + if (err) + break; + if (val == 255) + reg &= ~data->CRITICAL_PWM_ENABLE_MASK; + else + reg |= data->CRITICAL_PWM_ENABLE_MASK; + err = nct6775_write_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], reg); + break; + } + } + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static ssize_t +show_auto_temp(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 point = sattr->index; + + if (IS_ERR(data)) + return PTR_ERR(data); + + /* + * We don't know for sure if the temperature is signed or unsigned. + * Assume it is unsigned. + */ + return sprintf(buf, "%d\n", data->auto_temp[nr][point] * 1000); +} + +static ssize_t +store_auto_temp(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 point = sattr->index; + unsigned long val; + int err; + + err = kstrtoul(buf, 10, &val); + if (err) + return err; + if (val > 255000) + return -EINVAL; + + mutex_lock(&data->update_lock); + data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); + if (point < data->auto_pwm_num) { + err = nct6775_write_value(data, NCT6775_AUTO_TEMP(data, nr, point), + data->auto_temp[nr][point]); + } else { + err = nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], + data->auto_temp[nr][point]); + } + mutex_unlock(&data->update_lock); + return err ? : count; +} + +static umode_t nct6775_pwm_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + int pwm = index / 36; /* pwm index */ + int nr = index % 36; /* attribute index */ + + if (!(data->has_pwm & BIT(pwm))) + return 0; + + if ((nr >= 14 && nr <= 18) || nr == 21) /* weight */ + if (!data->REG_WEIGHT_TEMP_SEL[pwm]) + return 0; + if (nr == 19 && data->REG_PWM[3] == NULL) /* pwm_max */ + return 0; + if (nr == 20 && data->REG_PWM[4] == NULL) /* pwm_step */ + return 0; + if (nr == 21 && data->REG_PWM[6] == NULL) /* weight_duty_base */ + return 0; + + if (nr >= 22 && nr <= 35) { /* auto point */ + int api = (nr - 22) / 2; /* auto point index */ + + if (api > data->auto_pwm_num) + return 0; + } + return nct6775_attr_mode(data, attr); +} + +SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", 0644, show_fan_time, store_fan_time, 0, 0); +SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", 0644, + show_fan_time, store_fan_time, 0, 1); +SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", 0644, + show_fan_time, store_fan_time, 0, 2); +SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", 0644, show_pwm, store_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", 0644, show_pwm, store_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", 0644, + show_temp_tolerance, store_temp_tolerance, 0, 0); +SENSOR_TEMPLATE_2(pwm_crit_temp_tolerance, "pwm%d_crit_temp_tolerance", + 0644, show_temp_tolerance, store_temp_tolerance, 0, 1); + +SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", 0644, show_pwm, store_pwm, 0, 3); + +SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", 0644, show_pwm, store_pwm, 0, 4); + +SENSOR_TEMPLATE_2(pwm_auto_point1_pwm, "pwm%d_auto_point1_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 0); +SENSOR_TEMPLATE_2(pwm_auto_point1_temp, "pwm%d_auto_point1_temp", + 0644, show_auto_temp, store_auto_temp, 0, 0); + +SENSOR_TEMPLATE_2(pwm_auto_point2_pwm, "pwm%d_auto_point2_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 1); +SENSOR_TEMPLATE_2(pwm_auto_point2_temp, "pwm%d_auto_point2_temp", + 0644, show_auto_temp, store_auto_temp, 0, 1); + +SENSOR_TEMPLATE_2(pwm_auto_point3_pwm, "pwm%d_auto_point3_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 2); +SENSOR_TEMPLATE_2(pwm_auto_point3_temp, "pwm%d_auto_point3_temp", + 0644, show_auto_temp, store_auto_temp, 0, 2); + +SENSOR_TEMPLATE_2(pwm_auto_point4_pwm, "pwm%d_auto_point4_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 3); +SENSOR_TEMPLATE_2(pwm_auto_point4_temp, "pwm%d_auto_point4_temp", + 0644, show_auto_temp, store_auto_temp, 0, 3); + +SENSOR_TEMPLATE_2(pwm_auto_point5_pwm, "pwm%d_auto_point5_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 4); +SENSOR_TEMPLATE_2(pwm_auto_point5_temp, "pwm%d_auto_point5_temp", + 0644, show_auto_temp, store_auto_temp, 0, 4); + +SENSOR_TEMPLATE_2(pwm_auto_point6_pwm, "pwm%d_auto_point6_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 5); +SENSOR_TEMPLATE_2(pwm_auto_point6_temp, "pwm%d_auto_point6_temp", + 0644, show_auto_temp, store_auto_temp, 0, 5); + +SENSOR_TEMPLATE_2(pwm_auto_point7_pwm, "pwm%d_auto_point7_pwm", + 0644, show_auto_pwm, store_auto_pwm, 0, 6); +SENSOR_TEMPLATE_2(pwm_auto_point7_temp, "pwm%d_auto_point7_temp", + 0644, show_auto_temp, store_auto_temp, 0, 6); + +/* + * nct6775_pwm_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 *nct6775_attributes_pwm_template[] = { + &sensor_dev_template_pwm, + &sensor_dev_template_pwm_mode, + &sensor_dev_template_pwm_enable, + &sensor_dev_template_pwm_temp_sel, + &sensor_dev_template_pwm_temp_tolerance, + &sensor_dev_template_pwm_crit_temp_tolerance, + &sensor_dev_template_pwm_target_temp, + &sensor_dev_template_fan_target, + &sensor_dev_template_fan_tolerance, + &sensor_dev_template_pwm_stop_time, + &sensor_dev_template_pwm_step_up_time, + &sensor_dev_template_pwm_step_down_time, + &sensor_dev_template_pwm_start, + &sensor_dev_template_pwm_floor, + &sensor_dev_template_pwm_weight_temp_sel, /* 14 */ + &sensor_dev_template_pwm_weight_temp_step, + &sensor_dev_template_pwm_weight_temp_step_tol, + &sensor_dev_template_pwm_weight_temp_step_base, + &sensor_dev_template_pwm_weight_duty_step, /* 18 */ + &sensor_dev_template_pwm_max, /* 19 */ + &sensor_dev_template_pwm_step, /* 20 */ + &sensor_dev_template_pwm_weight_duty_base, /* 21 */ + &sensor_dev_template_pwm_auto_point1_pwm, /* 22 */ + &sensor_dev_template_pwm_auto_point1_temp, + &sensor_dev_template_pwm_auto_point2_pwm, + &sensor_dev_template_pwm_auto_point2_temp, + &sensor_dev_template_pwm_auto_point3_pwm, + &sensor_dev_template_pwm_auto_point3_temp, + &sensor_dev_template_pwm_auto_point4_pwm, + &sensor_dev_template_pwm_auto_point4_temp, + &sensor_dev_template_pwm_auto_point5_pwm, + &sensor_dev_template_pwm_auto_point5_temp, + &sensor_dev_template_pwm_auto_point6_pwm, + &sensor_dev_template_pwm_auto_point6_temp, + &sensor_dev_template_pwm_auto_point7_pwm, + &sensor_dev_template_pwm_auto_point7_temp, /* 35 */ + + NULL +}; + +static const struct sensor_template_group nct6775_pwm_template_group = { + .templates = nct6775_attributes_pwm_template, + .is_visible = nct6775_pwm_is_visible, + .base = 1, +}; + +static inline int nct6775_init_device(struct nct6775_data *data) +{ + int i, err; + u16 tmp, diode; + + /* Start monitoring if needed */ + if (data->REG_CONFIG) { + err = nct6775_read_value(data, data->REG_CONFIG, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); + if (err) + return err; + } + } + + /* Enable temperature sensors if needed */ + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + if (!data->reg_temp_config[i]) + continue; + err = nct6775_read_value(data, data->reg_temp_config[i], &tmp); + if (err) + return err; + if (tmp & 0x01) { + err = nct6775_write_value(data, data->reg_temp_config[i], tmp & 0xfe); + if (err) + return err; + } + } + + /* Enable VBAT monitoring if needed */ + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + return err; + if (!(tmp & 0x01)) { + err = nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); + if (err) + return err; + } + + err = nct6775_read_value(data, data->REG_DIODE, &diode); + if (err) + return err; + + for (i = 0; i < data->temp_fixed_num; i++) { + if (!(data->have_temp_fixed & BIT(i))) + continue; + if ((tmp & (data->DIODE_MASK << i))) /* diode */ + data->temp_type[i] + = 3 - ((diode >> i) & data->DIODE_MASK); + else /* thermistor */ + data->temp_type[i] = 4; + } + + return 0; +} + +static int add_temp_sensors(struct nct6775_data *data, const u16 *regp, + int *available, int *mask) +{ + int i, err; + u16 src; + + for (i = 0; i < data->pwm_num && *available; i++) { + int index; + + if (!regp[i]) + continue; + err = nct6775_read_value(data, regp[i], &src); + if (err) + return err; + src &= 0x1f; + if (!src || (*mask & BIT(src))) + continue; + if (!(data->temp_mask & BIT(src))) + continue; + + index = __ffs(*available); + err = nct6775_write_value(data, data->REG_TEMP_SOURCE[index], src); + if (err) + return err; + *available &= ~BIT(index); + *mask |= BIT(src); + } + + return 0; +} + +int nct6775_probe(struct device *dev, struct nct6775_data *data, + const struct regmap_config *regmapcfg) +{ + int i, s, err = 0; + int mask, available; + u16 src; + const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; + const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; + const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; + int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; + struct device *hwmon_dev; + struct sensor_template_group tsi_temp_tg; + + data->regmap = devm_regmap_init(dev, NULL, data, regmapcfg); + if (IS_ERR(data->regmap)) + return PTR_ERR(data->regmap); + + mutex_init(&data->update_lock); + data->name = nct6775_device_names[data->kind]; + data->bank = 0xff; /* Force initial bank selection */ + + switch (data->kind) { + case nct6106: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 4; + data->temp_fixed_num = 3; + data->num_temp_alarms = 6; + data->num_temp_beeps = 6; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_VBAT = NCT6106_REG_VBAT; + data->REG_DIODE = NCT6106_REG_DIODE; + data->DIODE_MASK = NCT6106_DIODE_MASK; + data->REG_VIN = NCT6106_REG_IN; + data->REG_IN_MINMAX[0] = NCT6106_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6106_REG_IN_MAX; + data->REG_TARGET = NCT6106_REG_TARGET; + data->REG_FAN = NCT6106_REG_FAN; + data->REG_FAN_MODE = NCT6106_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6106_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6106_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6106_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6106_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6106_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6106_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6106_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6116_REG_PWM; + data->REG_PWM[1] = NCT6106_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6106_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6106_REG_PWM_READ; + data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6106_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6106_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6106_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6106_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6106_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6106_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6106_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6106_REG_ALARM; + data->ALARM_BITS = NCT6106_ALARM_BITS; + data->REG_BEEP = NCT6106_REG_BEEP; + data->BEEP_BITS = NCT6106_BEEP_BITS; + data->REG_TSI_TEMP = NCT6106_REG_TSI_TEMP; + + reg_temp = NCT6106_REG_TEMP; + reg_temp_mon = NCT6106_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6106_REG_TSI_TEMP); + reg_temp_over = NCT6106_REG_TEMP_OVER; + reg_temp_hyst = NCT6106_REG_TEMP_HYST; + reg_temp_config = NCT6106_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6106_REG_TEMP_CRIT; + reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; + reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; + + break; + case nct6116: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 4; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 3; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_VBAT = NCT6106_REG_VBAT; + data->REG_DIODE = NCT6106_REG_DIODE; + data->DIODE_MASK = NCT6106_DIODE_MASK; + data->REG_VIN = NCT6106_REG_IN; + data->REG_IN_MINMAX[0] = NCT6106_REG_IN_MIN; + data->REG_IN_MINMAX[1] = NCT6106_REG_IN_MAX; + data->REG_TARGET = NCT6116_REG_TARGET; + data->REG_FAN = NCT6116_REG_FAN; + data->REG_FAN_MODE = NCT6116_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6116_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6116_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6116_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6116_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6116_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6116_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6116_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6116_REG_PWM; + data->REG_PWM[1] = NCT6116_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6116_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6106_REG_PWM_READ; + data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6116_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6116_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6116_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6116_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6116_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6106_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6116_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6116_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6106_REG_ALARM; + data->ALARM_BITS = NCT6116_ALARM_BITS; + data->REG_BEEP = NCT6106_REG_BEEP; + data->BEEP_BITS = NCT6116_BEEP_BITS; + data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP; + + reg_temp = NCT6106_REG_TEMP; + reg_temp_mon = NCT6106_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); + reg_temp_over = NCT6106_REG_TEMP_OVER; + reg_temp_hyst = NCT6106_REG_TEMP_HYST; + reg_temp_config = NCT6106_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6106_REG_TEMP_CRIT; + reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; + reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; + + break; + case nct6775: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 6; + data->has_fan_div = true; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 3; + + data->ALARM_BITS = NCT6775_ALARM_BITS; + data->BEEP_BITS = NCT6775_BEEP_BITS; + + data->fan_from_reg = fan_from_reg16; + data->fan_from_reg_min = fan_from_reg8; + data->target_temp_mask = 0x7f; + data->tolerance_mask = 0x0f; + data->speed_tolerance_limit = 15; + + data->temp_label = nct6775_temp_label; + data->temp_mask = NCT6775_TEMP_MASK; + data->virt_temp_mask = NCT6775_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + 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_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6775_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[3] = NCT6775_REG_FAN_MAX_OUTPUT; + data->REG_PWM[4] = NCT6775_REG_FAN_STEP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6775_REG_ALARM; + data->REG_BEEP = NCT6775_REG_BEEP; + data->REG_TSI_TEMP = NCT6775_REG_TSI_TEMP; + + reg_temp = NCT6775_REG_TEMP; + reg_temp_mon = NCT6775_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6775_REG_TSI_TEMP); + reg_temp_over = NCT6775_REG_TEMP_OVER; + reg_temp_hyst = NCT6775_REG_TEMP_HYST; + reg_temp_config = NCT6775_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6775_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6775_REG_TEMP_CRIT; + + break; + case nct6776: + data->in_num = 9; + data->pwm_num = 3; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 3; + data->num_temp_alarms = 3; + data->num_temp_beeps = 6; + + data->ALARM_BITS = NCT6776_ALARM_BITS; + data->BEEP_BITS = NCT6776_BEEP_BITS; + + data->fan_from_reg = fan_from_reg13; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + data->temp_label = nct6776_temp_label; + data->temp_mask = NCT6776_TEMP_MASK; + data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + 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_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6775_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6775_REG_ALARM; + data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; + + reg_temp = NCT6775_REG_TEMP; + reg_temp_mon = NCT6775_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); + reg_temp_over = NCT6775_REG_TEMP_OVER; + reg_temp_hyst = NCT6775_REG_TEMP_HYST; + reg_temp_config = NCT6776_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6776_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6776_REG_TEMP_CRIT; + + break; + case nct6779: + data->in_num = 15; + data->pwm_num = 5; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 6; + data->num_temp_alarms = 2; + data->num_temp_beeps = 2; + + data->ALARM_BITS = NCT6779_ALARM_BITS; + data->BEEP_BITS = NCT6779_BEEP_BITS; + + data->fan_from_reg = fan_from_reg_rpm; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + data->temp_label = nct6779_temp_label; + data->temp_mask = NCT6779_TEMP_MASK; + data->virt_temp_mask = NCT6779_VIRT_TEMP_MASK; + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + 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_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6779_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6779_REG_ALARM; + data->REG_BEEP = NCT6776_REG_BEEP; + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; + + reg_temp = NCT6779_REG_TEMP; + reg_temp_mon = NCT6779_REG_TEMP_MON; + num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); + num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); + reg_temp_over = NCT6779_REG_TEMP_OVER; + reg_temp_hyst = NCT6779_REG_TEMP_HYST; + reg_temp_config = NCT6779_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6779_REG_TEMP_CRIT; + + break; + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + data->in_num = 15; + data->pwm_num = (data->kind == nct6796 || + data->kind == nct6797 || + data->kind == nct6798) ? 7 : 6; + data->auto_pwm_num = 4; + data->has_fan_div = false; + data->temp_fixed_num = 6; + data->num_temp_alarms = 2; + data->num_temp_beeps = 2; + + data->ALARM_BITS = NCT6791_ALARM_BITS; + data->BEEP_BITS = NCT6779_BEEP_BITS; + + data->fan_from_reg = fan_from_reg_rpm; + data->fan_from_reg_min = fan_from_reg13; + data->target_temp_mask = 0xff; + data->tolerance_mask = 0x07; + data->speed_tolerance_limit = 63; + + switch (data->kind) { + default: + case nct6791: + data->temp_label = nct6779_temp_label; + data->temp_mask = NCT6791_TEMP_MASK; + data->virt_temp_mask = NCT6791_VIRT_TEMP_MASK; + break; + case nct6792: + data->temp_label = nct6792_temp_label; + data->temp_mask = NCT6792_TEMP_MASK; + data->virt_temp_mask = NCT6792_VIRT_TEMP_MASK; + break; + case nct6793: + data->temp_label = nct6793_temp_label; + data->temp_mask = NCT6793_TEMP_MASK; + data->virt_temp_mask = NCT6793_VIRT_TEMP_MASK; + break; + case nct6795: + case nct6797: + data->temp_label = nct6795_temp_label; + data->temp_mask = NCT6795_TEMP_MASK; + data->virt_temp_mask = NCT6795_VIRT_TEMP_MASK; + break; + case nct6796: + data->temp_label = nct6796_temp_label; + data->temp_mask = NCT6796_TEMP_MASK; + data->virt_temp_mask = NCT6796_VIRT_TEMP_MASK; + break; + case nct6798: + data->temp_label = nct6798_temp_label; + data->temp_mask = NCT6798_TEMP_MASK; + data->virt_temp_mask = NCT6798_VIRT_TEMP_MASK; + break; + } + + data->REG_CONFIG = NCT6775_REG_CONFIG; + data->REG_VBAT = NCT6775_REG_VBAT; + data->REG_DIODE = NCT6775_REG_DIODE; + data->DIODE_MASK = NCT6775_DIODE_MASK; + 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_TARGET = NCT6775_REG_TARGET; + data->REG_FAN = NCT6779_REG_FAN; + data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; + data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; + data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; + data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; + data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; + data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; + data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; + data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; + data->REG_PWM[0] = NCT6775_REG_PWM; + data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; + data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; + data->REG_PWM[5] = NCT6791_REG_WEIGHT_DUTY_STEP; + data->REG_PWM[6] = NCT6791_REG_WEIGHT_DUTY_BASE; + data->REG_PWM_READ = NCT6775_REG_PWM_READ; + data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; + data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; + data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; + data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; + data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; + data->REG_CRITICAL_TEMP_TOLERANCE + = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; + data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; + data->CRITICAL_PWM_ENABLE_MASK + = NCT6779_CRITICAL_PWM_ENABLE_MASK; + data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; + data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; + data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; + data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; + data->REG_WEIGHT_TEMP_SEL = NCT6791_REG_WEIGHT_TEMP_SEL; + data->REG_WEIGHT_TEMP[0] = NCT6791_REG_WEIGHT_TEMP_STEP; + data->REG_WEIGHT_TEMP[1] = NCT6791_REG_WEIGHT_TEMP_STEP_TOL; + data->REG_WEIGHT_TEMP[2] = NCT6791_REG_WEIGHT_TEMP_BASE; + data->REG_ALARM = NCT6791_REG_ALARM; + if (data->kind == nct6791) + data->REG_BEEP = NCT6776_REG_BEEP; + else + data->REG_BEEP = NCT6792_REG_BEEP; + switch (data->kind) { + case nct6791: + case nct6792: + case nct6793: + data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); + break; + case nct6795: + case nct6796: + case nct6797: + case nct6798: + data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; + num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); + break; + default: + num_reg_tsi_temp = 0; + break; + } + + reg_temp = NCT6779_REG_TEMP; + num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); + if (data->kind == nct6791) { + reg_temp_mon = NCT6779_REG_TEMP_MON; + num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); + } else { + reg_temp_mon = NCT6792_REG_TEMP_MON; + num_reg_temp_mon = ARRAY_SIZE(NCT6792_REG_TEMP_MON); + } + reg_temp_over = NCT6779_REG_TEMP_OVER; + reg_temp_hyst = NCT6779_REG_TEMP_HYST; + reg_temp_config = NCT6779_REG_TEMP_CONFIG; + reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; + reg_temp_crit = NCT6779_REG_TEMP_CRIT; + + break; + default: + return -ENODEV; + } + data->have_in = BIT(data->in_num) - 1; + data->have_temp = 0; + + /* + * On some boards, not all available temperature sources are monitored, + * even though some of the monitoring registers are unused. + * Get list of unused monitoring registers, then detect if any fan + * controls are configured to use unmonitored temperature sources. + * If so, assign the unmonitored temperature sources to available + * monitoring registers. + */ + mask = 0; + available = 0; + for (i = 0; i < num_reg_temp; i++) { + if (reg_temp[i] == 0) + continue; + + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; + if (!src || (mask & BIT(src))) + available |= BIT(i); + + mask |= BIT(src); + } + + /* + * Now find unmonitored temperature registers and enable monitoring + * if additional monitoring registers are available. + */ + err = add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); + if (err) + return err; + err = add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); + if (err) + return err; + + mask = 0; + s = NUM_TEMP_FIXED; /* First dynamic temperature attribute */ + for (i = 0; i < num_reg_temp; i++) { + if (reg_temp[i] == 0) + continue; + + err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); + if (err) + return err; + src &= 0x1f; + if (!src || (mask & BIT(src))) + continue; + + if (!(data->temp_mask & BIT(src))) { + dev_info(dev, + "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", + src, i, data->REG_TEMP_SOURCE[i], reg_temp[i]); + continue; + } + + mask |= BIT(src); + + /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ + if (src <= data->temp_fixed_num) { + data->have_temp |= BIT(src - 1); + data->have_temp_fixed |= BIT(src - 1); + data->reg_temp[0][src - 1] = reg_temp[i]; + data->reg_temp[1][src - 1] = reg_temp_over[i]; + data->reg_temp[2][src - 1] = reg_temp_hyst[i]; + if (reg_temp_crit_h && reg_temp_crit_h[i]) + data->reg_temp[3][src - 1] = reg_temp_crit_h[i]; + else if (reg_temp_crit[src - 1]) + data->reg_temp[3][src - 1] + = reg_temp_crit[src - 1]; + if (reg_temp_crit_l && reg_temp_crit_l[i]) + data->reg_temp[4][src - 1] = reg_temp_crit_l[i]; + data->reg_temp_config[src - 1] = reg_temp_config[i]; + data->temp_src[src - 1] = src; + continue; + } + + if (s >= NUM_TEMP) + continue; + + /* Use dynamic index for other sources */ + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp[i]; + data->reg_temp[1][s] = reg_temp_over[i]; + data->reg_temp[2][s] = reg_temp_hyst[i]; + data->reg_temp_config[s] = reg_temp_config[i]; + if (reg_temp_crit_h && reg_temp_crit_h[i]) + data->reg_temp[3][s] = reg_temp_crit_h[i]; + else if (reg_temp_crit[src - 1]) + data->reg_temp[3][s] = reg_temp_crit[src - 1]; + if (reg_temp_crit_l && reg_temp_crit_l[i]) + data->reg_temp[4][s] = reg_temp_crit_l[i]; + + data->temp_src[s] = src; + s++; + } + + /* + * Repeat with temperatures used for fan control. + * This set of registers does not support limits. + */ + for (i = 0; i < num_reg_temp_mon; i++) { + if (reg_temp_mon[i] == 0) + continue; + + err = nct6775_read_value(data, data->REG_TEMP_SEL[i], &src); + if (err) + return err; + src &= 0x1f; + if (!src) + continue; + + if (!(data->temp_mask & BIT(src))) { + dev_info(dev, + "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", + src, i, data->REG_TEMP_SEL[i], + reg_temp_mon[i]); + continue; + } + + /* + * For virtual temperature sources, the 'virtual' temperature + * for each fan reflects a different temperature, and there + * are no duplicates. + */ + if (!(data->virt_temp_mask & BIT(src))) { + if (mask & BIT(src)) + continue; + mask |= BIT(src); + } + + /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ + if (src <= data->temp_fixed_num) { + if (data->have_temp & BIT(src - 1)) + continue; + data->have_temp |= BIT(src - 1); + data->have_temp_fixed |= BIT(src - 1); + data->reg_temp[0][src - 1] = reg_temp_mon[i]; + data->temp_src[src - 1] = src; + continue; + } + + if (s >= NUM_TEMP) + continue; + + /* Use dynamic index for other sources */ + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp_mon[i]; + data->temp_src[s] = src; + s++; + } + +#ifdef USE_ALTERNATE + /* + * Go through the list of alternate temp registers and enable + * if possible. + * The temperature is already monitored if the respective bit in + * is set. + */ + for (i = 0; i < 31; i++) { + if (!(data->temp_mask & BIT(i + 1))) + continue; + if (!reg_temp_alternate[i]) + continue; + if (mask & BIT(i + 1)) + continue; + if (i < data->temp_fixed_num) { + if (data->have_temp & BIT(i)) + continue; + data->have_temp |= BIT(i); + data->have_temp_fixed |= BIT(i); + data->reg_temp[0][i] = reg_temp_alternate[i]; + if (i < num_reg_temp) { + data->reg_temp[1][i] = reg_temp_over[i]; + data->reg_temp[2][i] = reg_temp_hyst[i]; + } + data->temp_src[i] = i + 1; + continue; + } + + if (s >= NUM_TEMP) /* Abort if no more space */ + break; + + data->have_temp |= BIT(s); + data->reg_temp[0][s] = reg_temp_alternate[i]; + data->temp_src[s] = i + 1; + s++; + } +#endif /* USE_ALTERNATE */ + + /* Check which TSIx_TEMP registers are active */ + for (i = 0; i < num_reg_tsi_temp; i++) { + u16 tmp; + + err = nct6775_read_value(data, data->REG_TSI_TEMP[i], &tmp); + if (err) + return err; + if (tmp) + data->have_tsi_temp |= BIT(i); + } + + /* Initialize the chip */ + err = nct6775_init_device(data); + if (err) + return err; + + if (data->driver_init) { + err = data->driver_init(data); + if (err) + return err; + } + + /* Read fan clock dividers immediately */ + err = nct6775_init_fan_common(dev, data); + if (err) + return err; + + /* Register sysfs hooks */ + err = nct6775_add_template_attr_group(dev, data, &nct6775_pwm_template_group, + data->pwm_num); + if (err) + return err; + + err = nct6775_add_template_attr_group(dev, data, &nct6775_in_template_group, + fls(data->have_in)); + if (err) + return err; + + err = nct6775_add_template_attr_group(dev, data, &nct6775_fan_template_group, + fls(data->has_fan)); + if (err) + return err; + + err = nct6775_add_template_attr_group(dev, data, &nct6775_temp_template_group, + fls(data->have_temp)); + if (err) + return err; + + if (data->have_tsi_temp) { + tsi_temp_tg.templates = nct6775_tsi_temp_template; + tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; + tsi_temp_tg.base = fls(data->have_temp) + 1; + err = nct6775_add_template_attr_group(dev, data, &tsi_temp_tg, + fls(data->have_tsi_temp)); + if (err) + return err; + } + + hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, + data, data->groups); + return PTR_ERR_OR_ZERO(hwmon_dev); +} +EXPORT_SYMBOL_GPL(nct6775_probe); + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Core driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c new file mode 100644 index 000000000000..c2f76af735a2 --- /dev/null +++ b/drivers/hwmon/nct6775-platform.c @@ -0,0 +1,1226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * nct6775 - Platform driver for the hardware monitoring + * functionality of Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2012 Guenter Roeck + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nct6775.h" + +enum sensor_access { access_direct, access_asuswmi }; + +static const char * const nct6775_sio_names[] __initconst = { + "NCT6106D", + "NCT6116D", + "NCT6775F", + "NCT6776D/F", + "NCT6779D", + "NCT6791D", + "NCT6792D", + "NCT6793D", + "NCT6795D", + "NCT6796D", + "NCT6797D", + "NCT6798D", +}; + +static unsigned short force_id; +module_param(force_id, ushort, 0); +MODULE_PARM_DESC(force_id, "Override the detected device ID"); + +static unsigned short fan_debounce; +module_param(fan_debounce, ushort, 0); +MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); + +#define DRVNAME "nct6775" + +#define NCT6775_PORT_CHIPID 0x58 + +/* + * ISA constants + */ + +#define IOREGION_ALIGNMENT (~7) +#define IOREGION_OFFSET 5 +#define IOREGION_LENGTH 2 +#define ADDR_REG_OFFSET 0 +#define DATA_REG_OFFSET 1 + +/* + * Super-I/O constants and functions + */ + +#define NCT6775_LD_ACPI 0x0a +#define NCT6775_LD_HWM 0x0b +#define NCT6775_LD_VID 0x0d +#define NCT6775_LD_12 0x12 + +#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_NCT6106_ID 0xc450 +#define SIO_NCT6116_ID 0xd280 +#define SIO_NCT6775_ID 0xb470 +#define SIO_NCT6776_ID 0xc330 +#define SIO_NCT6779_ID 0xc560 +#define SIO_NCT6791_ID 0xc800 +#define SIO_NCT6792_ID 0xc910 +#define SIO_NCT6793_ID 0xd120 +#define SIO_NCT6795_ID 0xd350 +#define SIO_NCT6796_ID 0xd420 +#define SIO_NCT6797_ID 0xd450 +#define SIO_NCT6798_ID 0xd428 +#define SIO_ID_MASK 0xFFF8 + +/* + * Control registers + */ +#define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0 + +struct nct6775_sio_data { + int sioreg; + int ld; + enum kinds kind; + enum sensor_access access; + + /* superio_() callbacks */ + void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val); + int (*sio_inb)(struct nct6775_sio_data *sio_data, int reg); + void (*sio_select)(struct nct6775_sio_data *sio_data, int ld); + int (*sio_enter)(struct nct6775_sio_data *sio_data); + void (*sio_exit)(struct nct6775_sio_data *sio_data); +}; + +#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" +#define ASUSWMI_METHODID_RSIO 0x5253494F +#define ASUSWMI_METHODID_WSIO 0x5753494F +#define ASUSWMI_METHODID_RHWM 0x5248574D +#define ASUSWMI_METHODID_WHWM 0x5748574D +#define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE + +static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval) +{ +#if IS_ENABLED(CONFIG_ACPI_WMI) + u32 args = bank | (reg << 8) | (val << 16); + struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + union acpi_object *obj; + u32 tmp = ASUSWMI_UNSUPPORTED_METHOD; + + status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, + method_id, &input, &output); + + if (ACPI_FAILURE(status)) + return -EIO; + + obj = output.pointer; + if (obj && obj->type == ACPI_TYPE_INTEGER) + tmp = obj->integer.value; + + if (retval) + *retval = tmp; + + kfree(obj); + + if (tmp == ASUSWMI_UNSUPPORTED_METHOD) + return -ENODEV; + return 0; +#else + return -EOPNOTSUPP; +#endif +} + +static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val) +{ + return nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WHWM, bank, + reg, val, NULL); +} + +static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val) +{ + u32 ret, tmp = 0; + + ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank, + reg, 0, &tmp); + *val = tmp; + return ret; +} + +static int superio_wmi_inb(struct nct6775_sio_data *sio_data, int reg) +{ + int tmp = 0; + + nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RSIO, sio_data->ld, + reg, 0, &tmp); + return tmp; +} + +static void superio_wmi_outb(struct nct6775_sio_data *sio_data, int reg, int val) +{ + nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WSIO, sio_data->ld, + reg, val, NULL); +} + +static void superio_wmi_select(struct nct6775_sio_data *sio_data, int ld) +{ + sio_data->ld = ld; +} + +static int superio_wmi_enter(struct nct6775_sio_data *sio_data) +{ + return 0; +} + +static void superio_wmi_exit(struct nct6775_sio_data *sio_data) +{ +} + +static void superio_outb(struct nct6775_sio_data *sio_data, int reg, int val) +{ + int ioreg = sio_data->sioreg; + + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static int superio_inb(struct nct6775_sio_data *sio_data, int reg) +{ + int ioreg = sio_data->sioreg; + + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static void superio_select(struct nct6775_sio_data *sio_data, int ld) +{ + int ioreg = sio_data->sioreg; + + outb(SIO_REG_LDSEL, ioreg); + outb(ld, ioreg + 1); +} + +static int superio_enter(struct nct6775_sio_data *sio_data) +{ + int ioreg = sio_data->sioreg; + + /* + * Try to reserve and for exclusive access. + */ + if (!request_muxed_region(ioreg, 2, DRVNAME)) + return -EBUSY; + + outb(0x87, ioreg); + outb(0x87, ioreg); + + return 0; +} + +static void superio_exit(struct nct6775_sio_data *sio_data) +{ + int ioreg = sio_data->sioreg; + + outb(0xaa, ioreg); + outb(0x02, ioreg); + outb(0x02, ioreg + 1); + release_region(ioreg, 2); +} + +static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg) +{ + u8 bank = reg >> 8; + + data->bank = bank; +} + +static int nct6775_wmi_reg_read(void *ctx, unsigned int reg, unsigned int *val) +{ + struct nct6775_data *data = ctx; + int err, word_sized = nct6775_reg_is_word_sized(data, reg); + u8 tmp = 0; + u16 res; + + nct6775_wmi_set_bank(data, reg); + + err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); + if (err) + return err; + + res = tmp; + if (word_sized) { + err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp); + if (err) + return err; + + res = (res << 8) + tmp; + } + *val = res; + return 0; +} + +static int nct6775_wmi_reg_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + int res, word_sized = nct6775_reg_is_word_sized(data, reg); + + nct6775_wmi_set_bank(data, reg); + + if (word_sized) { + res = nct6775_asuswmi_write(data->bank, reg & 0xff, value >> 8); + if (res) + return res; + + res = nct6775_asuswmi_write(data->bank, (reg & 0xff) + 1, value); + } else { + res = nct6775_asuswmi_write(data->bank, reg & 0xff, value); + } + + return res; +} + +/* + * 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 int nct6775_reg_read(void *ctx, unsigned int reg, unsigned int *val) +{ + struct nct6775_data *data = ctx; + int word_sized = nct6775_reg_is_word_sized(data, reg); + + nct6775_set_bank(data, reg); + outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); + *val = inb_p(data->addr + DATA_REG_OFFSET); + if (word_sized) { + outb_p((reg & 0xff) + 1, + data->addr + ADDR_REG_OFFSET); + *val = (*val << 8) + inb_p(data->addr + DATA_REG_OFFSET); + } + return 0; +} + +static int nct6775_reg_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + int word_sized = nct6775_reg_is_word_sized(data, reg); + + 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); + return 0; +} + +static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) +{ + int val; + + val = sio_data->sio_inb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); + if (val & 0x10) { + pr_info("Enabling hardware monitor logical device mappings.\n"); + sio_data->sio_outb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, + val & ~0x10); + } +} + +static int __maybe_unused nct6775_suspend(struct device *dev) +{ + int err; + u16 tmp; + struct nct6775_data *data = dev_get_drvdata(dev); + + if (IS_ERR(data)) + return PTR_ERR(data); + + mutex_lock(&data->update_lock); + err = nct6775_read_value(data, data->REG_VBAT, &tmp); + if (err) + goto out; + data->vbat = tmp; + if (data->kind == nct6775) { + err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &tmp); + if (err) + goto out; + data->fandiv1 = tmp; + + err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &tmp); + if (err) + goto out; + data->fandiv2 = tmp; + } +out: + mutex_unlock(&data->update_lock); + + return err; +} + +static int __maybe_unused nct6775_resume(struct device *dev) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + int i, j, err = 0; + u8 reg; + + mutex_lock(&data->update_lock); + data->bank = 0xff; /* Force initial bank selection */ + + err = sio_data->sio_enter(sio_data); + if (err) + goto abort; + + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + reg = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + if (reg != data->sio_reg_enable) + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, data->sio_reg_enable); + + if (data->kind == nct6791 || data->kind == nct6792 || + data->kind == nct6793 || data->kind == nct6795 || + data->kind == nct6796 || data->kind == nct6797 || + data->kind == nct6798) + nct6791_enable_io_mapping(sio_data); + + sio_data->sio_exit(sio_data); + + /* Restore limits */ + for (i = 0; i < data->in_num; i++) { + if (!(data->have_in & BIT(i))) + continue; + + err = nct6775_write_value(data, data->REG_IN_MINMAX[0][i], data->in[i][1]); + if (err) + goto abort; + err = nct6775_write_value(data, data->REG_IN_MINMAX[1][i], data->in[i][2]); + if (err) + goto abort; + } + + for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { + if (!(data->has_fan_min & BIT(i))) + continue; + + err = nct6775_write_value(data, data->REG_FAN_MIN[i], data->fan_min[i]); + if (err) + goto abort; + } + + for (i = 0; i < NUM_TEMP; i++) { + if (!(data->have_temp & BIT(i))) + continue; + + for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) + if (data->reg_temp[j][i]) { + err = nct6775_write_temp(data, data->reg_temp[j][i], + data->temp[j][i]); + if (err) + goto abort; + } + } + + /* Restore other settings */ + err = nct6775_write_value(data, data->REG_VBAT, data->vbat); + if (err) + goto abort; + if (data->kind == nct6775) { + err = nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); + if (err) + goto abort; + err = nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); + } + +abort: + /* Force re-reading all values */ + data->valid = false; + mutex_unlock(&data->update_lock); + + return err; +} + +static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); + +static void +nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data) +{ + bool fan3pin = false, fan4pin = false, fan4min = false; + bool fan5pin = false, fan6pin = false, fan7pin = false; + bool pwm3pin = false, pwm4pin = false, pwm5pin = false; + bool pwm6pin = false, pwm7pin = false; + + /* Store SIO_REG_ENABLE for use during resume */ + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + data->sio_reg_enable = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + + /* fan4 and fan5 share some pins with the GPIO and serial flash */ + if (data->kind == nct6775) { + int cr2c = sio_data->sio_inb(sio_data, 0x2c); + + fan3pin = cr2c & BIT(6); + pwm3pin = cr2c & BIT(7); + + /* On NCT6775, fan4 shares pins with the fdc interface */ + fan4pin = !(sio_data->sio_inb(sio_data, 0x2A) & 0x80); + } else if (data->kind == nct6776) { + bool gpok = sio_data->sio_inb(sio_data, 0x27) & 0x80; + const char *board_vendor, *board_name; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name && board_vendor && + !strcmp(board_vendor, "ASRock")) { + /* + * Auxiliary fan monitoring is not enabled on ASRock + * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode. + * Observed with BIOS version 2.00. + */ + if (!strcmp(board_name, "Z77 Pro4-M")) { + if ((data->sio_reg_enable & 0xe0) != 0xe0) { + data->sio_reg_enable |= 0xe0; + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, + data->sio_reg_enable); + } + } + } + + if (data->sio_reg_enable & 0x80) + fan3pin = gpok; + else + fan3pin = !(sio_data->sio_inb(sio_data, 0x24) & 0x40); + + if (data->sio_reg_enable & 0x40) + fan4pin = gpok; + else + fan4pin = sio_data->sio_inb(sio_data, 0x1C) & 0x01; + + if (data->sio_reg_enable & 0x20) + fan5pin = gpok; + else + fan5pin = sio_data->sio_inb(sio_data, 0x1C) & 0x02; + + fan4min = fan4pin; + pwm3pin = fan3pin; + } else if (data->kind == nct6106) { + int cr24 = sio_data->sio_inb(sio_data, 0x24); + + fan3pin = !(cr24 & 0x80); + pwm3pin = cr24 & 0x08; + } else if (data->kind == nct6116) { + int cr1a = sio_data->sio_inb(sio_data, 0x1a); + int cr1b = sio_data->sio_inb(sio_data, 0x1b); + int cr24 = sio_data->sio_inb(sio_data, 0x24); + int cr2a = sio_data->sio_inb(sio_data, 0x2a); + int cr2b = sio_data->sio_inb(sio_data, 0x2b); + int cr2f = sio_data->sio_inb(sio_data, 0x2f); + + fan3pin = !(cr2b & 0x10); + fan4pin = (cr2b & 0x80) || // pin 1(2) + (!(cr2f & 0x10) && (cr1a & 0x04)); // pin 65(66) + fan5pin = (cr2b & 0x80) || // pin 126(127) + (!(cr1b & 0x03) && (cr2a & 0x02)); // pin 94(96) + + pwm3pin = fan3pin && (cr24 & 0x08); + pwm4pin = fan4pin; + pwm5pin = fan5pin; + } else { + /* + * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, + * NCT6797D, NCT6798D + */ + int cr1a = sio_data->sio_inb(sio_data, 0x1a); + int cr1b = sio_data->sio_inb(sio_data, 0x1b); + int cr1c = sio_data->sio_inb(sio_data, 0x1c); + int cr1d = sio_data->sio_inb(sio_data, 0x1d); + int cr2a = sio_data->sio_inb(sio_data, 0x2a); + int cr2b = sio_data->sio_inb(sio_data, 0x2b); + int cr2d = sio_data->sio_inb(sio_data, 0x2d); + int cr2f = sio_data->sio_inb(sio_data, 0x2f); + bool dsw_en = cr2f & BIT(3); + bool ddr4_en = cr2f & BIT(4); + int cre0; + int creb; + int cred; + + sio_data->sio_select(sio_data, NCT6775_LD_12); + cre0 = sio_data->sio_inb(sio_data, 0xe0); + creb = sio_data->sio_inb(sio_data, 0xeb); + cred = sio_data->sio_inb(sio_data, 0xed); + + fan3pin = !(cr1c & BIT(5)); + fan4pin = !(cr1c & BIT(6)); + fan5pin = !(cr1c & BIT(7)); + + pwm3pin = !(cr1c & BIT(0)); + pwm4pin = !(cr1c & BIT(1)); + pwm5pin = !(cr1c & BIT(2)); + + switch (data->kind) { + case nct6791: + fan6pin = cr2d & BIT(1); + pwm6pin = cr2d & BIT(0); + break; + case nct6792: + fan6pin = !dsw_en && (cr2d & BIT(1)); + pwm6pin = !dsw_en && (cr2d & BIT(0)); + break; + case nct6793: + fan5pin |= cr1b & BIT(5); + fan5pin |= creb & BIT(5); + + fan6pin = !dsw_en && (cr2d & BIT(1)); + fan6pin |= creb & BIT(3); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = !dsw_en && (cr2d & BIT(0)); + pwm6pin |= creb & BIT(2); + break; + case nct6795: + fan5pin |= cr1b & BIT(5); + fan5pin |= creb & BIT(5); + + fan6pin = (cr2a & BIT(4)) && + (!dsw_en || (cred & BIT(4))); + fan6pin |= creb & BIT(3); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); + pwm6pin |= creb & BIT(2); + break; + case nct6796: + fan5pin |= cr1b & BIT(5); + fan5pin |= (cre0 & BIT(3)) && !(cr1b & BIT(0)); + fan5pin |= creb & BIT(5); + + fan6pin = (cr2a & BIT(4)) && + (!dsw_en || (cred & BIT(4))); + fan6pin |= creb & BIT(3); + + fan7pin = !(cr2b & BIT(2)); + + pwm5pin |= cr2d & BIT(7); + pwm5pin |= (cre0 & BIT(4)) && !(cr1b & BIT(0)); + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); + pwm6pin |= creb & BIT(2); + + pwm7pin = !(cr1d & (BIT(2) | BIT(3))); + break; + case nct6797: + fan5pin |= !ddr4_en && (cr1b & BIT(5)); + fan5pin |= creb & BIT(5); + + fan6pin = cr2a & BIT(4); + fan6pin |= creb & BIT(3); + + fan7pin = cr1a & BIT(1); + + pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + pwm5pin |= !ddr4_en && (cr2d & BIT(7)); + + pwm6pin = creb & BIT(2); + pwm6pin |= cred & BIT(2); + + pwm7pin = cr1d & BIT(4); + break; + case nct6798: + fan6pin = !(cr1b & BIT(0)) && (cre0 & BIT(3)); + fan6pin |= cr2a & BIT(4); + fan6pin |= creb & BIT(5); + + fan7pin = cr1b & BIT(5); + fan7pin |= !(cr2b & BIT(2)); + fan7pin |= creb & BIT(3); + + pwm6pin = !(cr1b & BIT(0)) && (cre0 & BIT(4)); + pwm6pin |= !(cred & BIT(2)) && (cr2a & BIT(3)); + pwm6pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); + + pwm7pin = !(cr1d & (BIT(2) | BIT(3))); + pwm7pin |= cr2d & BIT(7); + pwm7pin |= creb & BIT(2); + break; + default: /* NCT6779D */ + break; + } + + fan4min = fan4pin; + } + + /* fan 1 and 2 (0x03) are always present */ + data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) | + (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); + data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | + (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); +} + +static ssize_t +cpu0_vid_show(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_RO(cpu0_vid); + +/* Case open detection */ + +static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; +static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; + +static ssize_t +clear_caseopen(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nct6775_data *data = dev_get_drvdata(dev); + struct nct6775_sio_data *sio_data = data->driver_data; + int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; + 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. + * The CR registers are the same for all chips, and not all chips + * support clearing the caseopen status through "regular" registers. + */ + ret = sio_data->sio_enter(sio_data); + if (ret) { + count = ret; + goto error; + } + + sio_data->sio_select(sio_data, NCT6775_LD_ACPI); + reg = sio_data->sio_inb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr]); + reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr]; + sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); + sio_data->sio_exit(sio_data); + + data->valid = false; /* Force cache refresh */ +error: + mutex_unlock(&data->update_lock); + return count; +} + +static SENSOR_DEVICE_ATTR(intrusion0_alarm, 0644, nct6775_show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_alarm, 0644, nct6775_show_alarm, + clear_caseopen, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(intrusion0_beep, 0644, nct6775_show_beep, + nct6775_store_beep, INTRUSION_ALARM_BASE); +static SENSOR_DEVICE_ATTR(intrusion1_beep, 0644, nct6775_show_beep, + nct6775_store_beep, INTRUSION_ALARM_BASE + 1); +static SENSOR_DEVICE_ATTR(beep_enable, 0644, nct6775_show_beep, + nct6775_store_beep, BEEP_ENABLE_BASE); + +static umode_t nct6775_other_is_visible(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = kobj_to_dev(kobj); + struct nct6775_data *data = dev_get_drvdata(dev); + + if (index == 0 && !data->have_vid) + return 0; + + if (index == 1 || index == 2) { + if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0) + return 0; + } + + if (index == 3 || index == 4) { + if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0) + return 0; + } + + return nct6775_attr_mode(data, attr); +} + +/* + * nct6775_other_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 attribute *nct6775_attributes_other[] = { + &dev_attr_cpu0_vid.attr, /* 0 */ + &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */ + &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */ + &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */ + &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */ + &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */ + + NULL +}; + +static const struct attribute_group nct6775_group_other = { + .attrs = nct6775_attributes_other, + .is_visible = nct6775_other_is_visible, +}; + +static int nct6775_platform_probe_init(struct nct6775_data *data) +{ + int err; + u8 cr2a; + struct nct6775_sio_data *sio_data = data->driver_data; + + err = sio_data->sio_enter(sio_data); + if (err) + return err; + + cr2a = sio_data->sio_inb(sio_data, 0x2a); + switch (data->kind) { + case nct6775: + data->have_vid = (cr2a & 0x40); + break; + case nct6776: + data->have_vid = (cr2a & 0x60) == 0x40; + break; + case nct6106: + case nct6116: + case nct6779: + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + break; + } + + /* + * Read VID value + * We can get the VID input values directly at logical device D 0xe3. + */ + if (data->have_vid) { + sio_data->sio_select(sio_data, NCT6775_LD_VID); + data->vid = sio_data->sio_inb(sio_data, 0xe3); + data->vrm = vid_which_vrm(); + } + + if (fan_debounce) { + u8 tmp; + + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + tmp = sio_data->sio_inb(sio_data, + NCT6775_REG_CR_FAN_DEBOUNCE); + switch (data->kind) { + case nct6106: + case nct6116: + tmp |= 0xe0; + break; + case nct6775: + tmp |= 0x1e; + break; + case nct6776: + case nct6779: + tmp |= 0x3e; + break; + case nct6791: + case nct6792: + case nct6793: + case nct6795: + case nct6796: + case nct6797: + case nct6798: + tmp |= 0x7e; + break; + } + sio_data->sio_outb(sio_data, NCT6775_REG_CR_FAN_DEBOUNCE, + tmp); + pr_info("Enabled fan debounce for chip %s\n", data->name); + } + + nct6775_check_fan_inputs(data, sio_data); + + sio_data->sio_exit(sio_data); + + return nct6775_add_attr_group(data, &nct6775_group_other); +} + +static const struct regmap_config nct6775_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_reg_read, + .reg_write = nct6775_reg_write, +}; + +static const struct regmap_config nct6775_wmi_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_wmi_reg_read, + .reg_write = nct6775_wmi_reg_write, +}; + +static int nct6775_platform_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct nct6775_sio_data *sio_data = dev_get_platdata(dev); + struct nct6775_data *data; + struct resource *res; + const struct regmap_config *regmapcfg; + + if (sio_data->access == access_direct) { + 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(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = sio_data->kind; + data->sioreg = sio_data->sioreg; + + if (sio_data->access == access_direct) { + data->addr = res->start; + regmapcfg = &nct6775_regmap_config; + } else { + regmapcfg = &nct6775_wmi_regmap_config; + } + + platform_set_drvdata(pdev, data); + + data->driver_data = sio_data; + data->driver_init = nct6775_platform_probe_init; + + return nct6775_probe(&pdev->dev, data, regmapcfg); +} + +static struct platform_driver nct6775_driver = { + .driver = { + .name = DRVNAME, + .pm = &nct6775_dev_pm_ops, + }, + .probe = nct6775_platform_probe, +}; + +/* nct6775_find() looks for a '627 in the Super-I/O config space */ +static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) +{ + u16 val; + int err; + int addr; + + sio_data->access = access_direct; + sio_data->sioreg = sioaddr; + + err = sio_data->sio_enter(sio_data); + if (err) + return err; + + val = (sio_data->sio_inb(sio_data, SIO_REG_DEVID) << 8) | + sio_data->sio_inb(sio_data, SIO_REG_DEVID + 1); + if (force_id && val != 0xffff) + val = force_id; + + switch (val & SIO_ID_MASK) { + case SIO_NCT6106_ID: + sio_data->kind = nct6106; + break; + case SIO_NCT6116_ID: + sio_data->kind = nct6116; + break; + case SIO_NCT6775_ID: + sio_data->kind = nct6775; + break; + case SIO_NCT6776_ID: + sio_data->kind = nct6776; + break; + case SIO_NCT6779_ID: + sio_data->kind = nct6779; + break; + case SIO_NCT6791_ID: + sio_data->kind = nct6791; + break; + case SIO_NCT6792_ID: + sio_data->kind = nct6792; + break; + case SIO_NCT6793_ID: + sio_data->kind = nct6793; + break; + case SIO_NCT6795_ID: + sio_data->kind = nct6795; + break; + case SIO_NCT6796_ID: + sio_data->kind = nct6796; + break; + case SIO_NCT6797_ID: + sio_data->kind = nct6797; + break; + case SIO_NCT6798_ID: + sio_data->kind = nct6798; + break; + default: + if (val != 0xffff) + pr_debug("unsupported chip ID: 0x%04x\n", val); + sio_data->sio_exit(sio_data); + return -ENODEV; + } + + /* We have a known chip, find the HWM I/O address */ + sio_data->sio_select(sio_data, NCT6775_LD_HWM); + val = (sio_data->sio_inb(sio_data, SIO_REG_ADDR) << 8) + | sio_data->sio_inb(sio_data, 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"); + sio_data->sio_exit(sio_data); + return -ENODEV; + } + + /* Activate logical device if needed */ + val = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); + if (!(val & 0x01)) { + pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); + sio_data->sio_outb(sio_data, SIO_REG_ENABLE, val | 0x01); + } + + if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || + sio_data->kind == nct6793 || sio_data->kind == nct6795 || + sio_data->kind == nct6796 || sio_data->kind == nct6797 || + sio_data->kind == nct6798) + nct6791_enable_io_mapping(sio_data); + + sio_data->sio_exit(sio_data); + pr_info("Found %s or compatible chip at %#x:%#x\n", + nct6775_sio_names[sio_data->kind], sioaddr, addr); + + return addr; +} + +/* + * 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 use platform_device_alloc(), we + * must keep track of the device + */ +static struct platform_device *pdev[2]; + +static const char * const asus_wmi_boards[] = { + "ProArt X570-CREATOR WIFI", + "Pro B550M-C", + "Pro WS X570-ACE", + "PRIME B360-PLUS", + "PRIME B460-PLUS", + "PRIME B550-PLUS", + "PRIME B550M-A", + "PRIME B550M-A (WI-FI)", + "PRIME X570-P", + "PRIME X570-PRO", + "ROG CROSSHAIR VIII DARK HERO", + "ROG CROSSHAIR VIII FORMULA", + "ROG CROSSHAIR VIII HERO", + "ROG CROSSHAIR VIII IMPACT", + "ROG STRIX B550-A GAMING", + "ROG STRIX B550-E GAMING", + "ROG STRIX B550-F GAMING", + "ROG STRIX B550-F GAMING (WI-FI)", + "ROG STRIX B550-F GAMING WIFI II", + "ROG STRIX B550-I GAMING", + "ROG STRIX B550-XE GAMING (WI-FI)", + "ROG STRIX X570-E GAMING", + "ROG STRIX X570-F GAMING", + "ROG STRIX X570-I GAMING", + "ROG STRIX Z390-E GAMING", + "ROG STRIX Z390-F GAMING", + "ROG STRIX Z390-H GAMING", + "ROG STRIX Z390-I GAMING", + "ROG STRIX Z490-A GAMING", + "ROG STRIX Z490-E GAMING", + "ROG STRIX Z490-F GAMING", + "ROG STRIX Z490-G GAMING", + "ROG STRIX Z490-G GAMING (WI-FI)", + "ROG STRIX Z490-H GAMING", + "ROG STRIX Z490-I GAMING", + "TUF GAMING B550M-PLUS", + "TUF GAMING B550M-PLUS (WI-FI)", + "TUF GAMING B550-PLUS", + "TUF GAMING B550-PRO", + "TUF GAMING X570-PLUS", + "TUF GAMING X570-PLUS (WI-FI)", + "TUF GAMING X570-PRO (WI-FI)", + "TUF GAMING Z490-PLUS", + "TUF GAMING Z490-PLUS (WI-FI)", +}; + +static int __init sensors_nct6775_platform_init(void) +{ + int i, err; + bool found = false; + int address; + struct resource res; + struct nct6775_sio_data sio_data; + int sioaddr[2] = { 0x2e, 0x4e }; + enum sensor_access access = access_direct; + const char *board_vendor, *board_name; + u8 tmp; + + err = platform_driver_register(&nct6775_driver); + if (err) + return err; + + board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name && board_vendor && + !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) { + err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards), + board_name); + if (err >= 0) { + /* if reading chip id via WMI succeeds, use WMI */ + if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { + pr_info("Using Asus WMI to access %#x chip.\n", tmp); + access = access_asuswmi; + } else { + pr_err("Can't read ChipID by Asus WMI.\n"); + } + } + } + + /* + * 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() + */ + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + sio_data.sio_outb = superio_outb; + sio_data.sio_inb = superio_inb; + sio_data.sio_select = superio_select; + sio_data.sio_enter = superio_enter; + sio_data.sio_exit = superio_exit; + + address = nct6775_find(sioaddr[i], &sio_data); + if (address <= 0) + continue; + + found = true; + + sio_data.access = access; + + if (access == access_asuswmi) { + sio_data.sio_outb = superio_wmi_outb; + sio_data.sio_inb = superio_wmi_inb; + sio_data.sio_select = superio_wmi_select; + sio_data.sio_enter = superio_wmi_enter; + sio_data.sio_exit = superio_wmi_exit; + } + + 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 nct6775_sio_data)); + if (err) + goto exit_device_put; + + if (sio_data.access == access_direct) { + 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(&nct6775_driver); + return err; +} + +static void __exit sensors_nct6775_platform_exit(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pdev); i++) { + if (pdev[i]) + platform_device_unregister(pdev[i]); + } + platform_driver_unregister(&nct6775_driver); +} + +MODULE_AUTHOR("Guenter Roeck "); +MODULE_DESCRIPTION("Platform driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(HWMON_NCT6775); + +module_init(sensors_nct6775_platform_init); +module_exit(sensors_nct6775_platform_exit); diff --git a/drivers/hwmon/nct6775.c b/drivers/hwmon/nct6775.c deleted file mode 100644 index 5e741bcf2645..000000000000 --- a/drivers/hwmon/nct6775.c +++ /dev/null @@ -1,5606 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * nct6775 - Driver for the hardware monitoring functionality of - * Nuvoton NCT677x Super-I/O chips - * - * Copyright (C) 2012 Guenter Roeck - * - * Derived from w83627ehf driver - * Copyright (C) 2005-2012 Jean Delvare - * Copyright (C) 2006 Yuan Mu (Winbond), - * Rudolf Marek - * David Hubbard - * Daniel J Blueman - * Copyright (C) 2010 Sheng-Yuan Huang (Nuvoton) (PS00) - * - * Shamelessly ripped from the w83627hf driver - * Copyright (C) 2003 Mark Studebaker - * - * Supports the following chips: - * - * Chip #vin #fan #pwm #temp chip IDs man ID - * nct6106d 9 3 3 6+3 0xc450 0xc1 0x5ca3 - * nct6116d 9 5 5 3+3 0xd280 0xc1 0x5ca3 - * 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 - * nct6791d 15 6 6 2+6 0xc800 0xc1 0x5ca3 - * nct6792d 15 6 6 2+6 0xc910 0xc1 0x5ca3 - * nct6793d 15 6 6 2+6 0xd120 0xc1 0x5ca3 - * nct6795d 14 6 6 2+6 0xd350 0xc1 0x5ca3 - * nct6796d 14 7 7 2+6 0xd420 0xc1 0x5ca3 - * nct6797d 14 7 7 2+6 0xd450 0xc1 0x5ca3 - * (0xd451) - * nct6798d 14 7 7 2+6 0xd428 0xc1 0x5ca3 - * (0xd429) - * - * #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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "lm75.h" - -#define USE_ALTERNATE - -enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, - nct6793, nct6795, nct6796, nct6797, nct6798 }; - -/* used to set data->name = nct6775_device_names[data->sio_kind] */ -static const char * const nct6775_device_names[] = { - "nct6106", - "nct6116", - "nct6775", - "nct6776", - "nct6779", - "nct6791", - "nct6792", - "nct6793", - "nct6795", - "nct6796", - "nct6797", - "nct6798", -}; - -static const char * const nct6775_sio_names[] __initconst = { - "NCT6106D", - "NCT6116D", - "NCT6775F", - "NCT6776D/F", - "NCT6779D", - "NCT6791D", - "NCT6792D", - "NCT6793D", - "NCT6795D", - "NCT6796D", - "NCT6797D", - "NCT6798D", -}; - -static unsigned short force_id; -module_param(force_id, ushort, 0); -MODULE_PARM_DESC(force_id, "Override the detected device ID"); - -static unsigned short fan_debounce; -module_param(fan_debounce, ushort, 0); -MODULE_PARM_DESC(fan_debounce, "Enable debouncing for fan RPM signal"); - -#define DRVNAME "nct6775" - -/* - * Super-I/O constants and functions - */ - -#define NCT6775_LD_ACPI 0x0a -#define NCT6775_LD_HWM 0x0b -#define NCT6775_LD_VID 0x0d -#define NCT6775_LD_12 0x12 - -#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_NCT6106_ID 0xc450 -#define SIO_NCT6116_ID 0xd280 -#define SIO_NCT6775_ID 0xb470 -#define SIO_NCT6776_ID 0xc330 -#define SIO_NCT6779_ID 0xc560 -#define SIO_NCT6791_ID 0xc800 -#define SIO_NCT6792_ID 0xc910 -#define SIO_NCT6793_ID 0xd120 -#define SIO_NCT6795_ID 0xd350 -#define SIO_NCT6796_ID 0xd420 -#define SIO_NCT6797_ID 0xd450 -#define SIO_NCT6798_ID 0xd428 -#define SIO_ID_MASK 0xFFF8 - -enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; -enum sensor_access { access_direct, access_asuswmi }; - -struct nct6775_sio_data { - int sioreg; - int ld; - enum kinds kind; - enum sensor_access access; - - /* superio_() callbacks */ - void (*sio_outb)(struct nct6775_sio_data *sio_data, int reg, int val); - int (*sio_inb)(struct nct6775_sio_data *sio_data, int reg); - void (*sio_select)(struct nct6775_sio_data *sio_data, int ld); - int (*sio_enter)(struct nct6775_sio_data *sio_data); - void (*sio_exit)(struct nct6775_sio_data *sio_data); -}; - -#define ASUSWMI_MONITORING_GUID "466747A0-70EC-11DE-8A39-0800200C9A66" -#define ASUSWMI_METHODID_RSIO 0x5253494F -#define ASUSWMI_METHODID_WSIO 0x5753494F -#define ASUSWMI_METHODID_RHWM 0x5248574D -#define ASUSWMI_METHODID_WHWM 0x5748574D -#define ASUSWMI_UNSUPPORTED_METHOD 0xFFFFFFFE - -static int nct6775_asuswmi_evaluate_method(u32 method_id, u8 bank, u8 reg, u8 val, u32 *retval) -{ -#if IS_ENABLED(CONFIG_ACPI_WMI) - u32 args = bank | (reg << 8) | (val << 16); - struct acpi_buffer input = { (acpi_size) sizeof(args), &args }; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - acpi_status status; - union acpi_object *obj; - u32 tmp = ASUSWMI_UNSUPPORTED_METHOD; - - status = wmi_evaluate_method(ASUSWMI_MONITORING_GUID, 0, - method_id, &input, &output); - - if (ACPI_FAILURE(status)) - return -EIO; - - obj = output.pointer; - if (obj && obj->type == ACPI_TYPE_INTEGER) - tmp = obj->integer.value; - - if (retval) - *retval = tmp; - - kfree(obj); - - if (tmp == ASUSWMI_UNSUPPORTED_METHOD) - return -ENODEV; - return 0; -#else - return -EOPNOTSUPP; -#endif -} - -static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val) -{ - return nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WHWM, bank, - reg, val, NULL); -} - -static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val) -{ - u32 ret, tmp = 0; - - ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank, - reg, 0, &tmp); - *val = tmp; - return ret; -} - -static int superio_wmi_inb(struct nct6775_sio_data *sio_data, int reg) -{ - int tmp = 0; - - nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RSIO, sio_data->ld, - reg, 0, &tmp); - return tmp; -} - -static void superio_wmi_outb(struct nct6775_sio_data *sio_data, int reg, int val) -{ - nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_WSIO, sio_data->ld, - reg, val, NULL); -} - -static void superio_wmi_select(struct nct6775_sio_data *sio_data, int ld) -{ - sio_data->ld = ld; -} - -static int superio_wmi_enter(struct nct6775_sio_data *sio_data) -{ - return 0; -} - -static void superio_wmi_exit(struct nct6775_sio_data *sio_data) -{ -} - -static void superio_outb(struct nct6775_sio_data *sio_data, int reg, int val) -{ - int ioreg = sio_data->sioreg; - - outb(reg, ioreg); - outb(val, ioreg + 1); -} - -static int superio_inb(struct nct6775_sio_data *sio_data, int reg) -{ - int ioreg = sio_data->sioreg; - - outb(reg, ioreg); - return inb(ioreg + 1); -} - -static void superio_select(struct nct6775_sio_data *sio_data, int ld) -{ - int ioreg = sio_data->sioreg; - - outb(SIO_REG_LDSEL, ioreg); - outb(ld, ioreg + 1); -} - -static int superio_enter(struct nct6775_sio_data *sio_data) -{ - int ioreg = sio_data->sioreg; - - /* - * Try to reserve and for exclusive access. - */ - if (!request_muxed_region(ioreg, 2, DRVNAME)) - return -EBUSY; - - outb(0x87, ioreg); - outb(0x87, ioreg); - - return 0; -} - -static void superio_exit(struct nct6775_sio_data *sio_data) -{ - int ioreg = sio_data->sioreg; - - 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 -#define NCT6775_PORT_CHIPID 0x58 - -/* - * 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_TEMP 10 /* Max number of temp attribute sets w/ limits*/ -#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ -#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ - -#define NUM_REG_ALARM 7 /* Max number of alarm registers */ -#define NUM_REG_BEEP 5 /* Max number of beep registers */ - -#define NUM_FAN 7 - -/* 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 -#define NCT6775_REG_DIODE 0x5E -#define NCT6775_DIODE_MASK 0x02 - -#define NCT6775_REG_FANDIV1 0x506 -#define NCT6775_REG_FANDIV2 0x507 - -#define NCT6775_REG_CR_FAN_DEBOUNCE 0xf0 - -static const u16 NCT6775_REG_ALARM[NUM_REG_ALARM] = { 0x459, 0x45A, 0x45B }; - -/* 0..15 voltages, 16..23 fans, 24..29 temperatures, 30..31 intrusion */ - -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, -1, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, -1 }; /* intrusion0, intrusion1 */ - -#define FAN_ALARM_BASE 16 -#define TEMP_ALARM_BASE 24 -#define INTRUSION_ALARM_BASE 30 - -static const u16 NCT6775_REG_BEEP[NUM_REG_BEEP] = { 0x56, 0x57, 0x453, 0x4e }; - -/* - * 0..14 voltages, 15 global beep enable, 16..23 fans, 24..29 temperatures, - * 30..31 intrusion - */ -static const s8 NCT6775_BEEP_BITS[] = { - 0, 1, 2, 3, 8, 9, 10, 16, /* in0.. in7 */ - 17, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - 21, /* global beep enable */ - 6, 7, 11, 28, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, -1 }; /* intrusion0, intrusion1 */ - -#define BEEP_ENABLE_BASE 15 - -static const u8 NCT6775_REG_CR_CASEOPEN_CLR[] = { 0xe6, 0xee }; -static const u8 NCT6775_CR_CASEOPEN_CLR_MASK[] = { 0x20, 0x01 }; - -/* DC or PWM output fan configuration */ -static const u8 NCT6775_REG_PWM_MODE[] = { 0x04, 0x04, 0x12 }; -static const u8 NCT6775_PWM_MODE_MASK[] = { 0x01, 0x02, 0x01 }; - -/* Advanced Fan control, some values are common for all fans */ - -static const u16 NCT6775_REG_TARGET[] = { - 0x101, 0x201, 0x301, 0x801, 0x901, 0xa01, 0xb01 }; -static const u16 NCT6775_REG_FAN_MODE[] = { - 0x102, 0x202, 0x302, 0x802, 0x902, 0xa02, 0xb02 }; -static const u16 NCT6775_REG_FAN_STEP_DOWN_TIME[] = { - 0x103, 0x203, 0x303, 0x803, 0x903, 0xa03, 0xb03 }; -static const u16 NCT6775_REG_FAN_STEP_UP_TIME[] = { - 0x104, 0x204, 0x304, 0x804, 0x904, 0xa04, 0xb04 }; -static const u16 NCT6775_REG_FAN_STOP_OUTPUT[] = { - 0x105, 0x205, 0x305, 0x805, 0x905, 0xa05, 0xb05 }; -static const u16 NCT6775_REG_FAN_START_OUTPUT[] = { - 0x106, 0x206, 0x306, 0x806, 0x906, 0xa06, 0xb06 }; -static const u16 NCT6775_REG_FAN_MAX_OUTPUT[] = { 0x10a, 0x20a, 0x30a }; -static const u16 NCT6775_REG_FAN_STEP_OUTPUT[] = { 0x10b, 0x20b, 0x30b }; - -static const u16 NCT6775_REG_FAN_STOP_TIME[] = { - 0x107, 0x207, 0x307, 0x807, 0x907, 0xa07, 0xb07 }; -static const u16 NCT6775_REG_PWM[] = { - 0x109, 0x209, 0x309, 0x809, 0x909, 0xa09, 0xb09 }; -static const u16 NCT6775_REG_PWM_READ[] = { - 0x01, 0x03, 0x11, 0x13, 0x15, 0xa09, 0xb09 }; - -static const u16 NCT6775_REG_FAN[] = { 0x630, 0x632, 0x634, 0x636, 0x638 }; -static const u16 NCT6775_REG_FAN_MIN[] = { 0x3b, 0x3c, 0x3d }; -static const u16 NCT6775_REG_FAN_PULSES[NUM_FAN] = { - 0x641, 0x642, 0x643, 0x644 }; -static const u16 NCT6775_FAN_PULSE_SHIFT[NUM_FAN] = { }; - -static const u16 NCT6775_REG_TEMP[] = { - 0x27, 0x150, 0x250, 0x62b, 0x62c, 0x62d }; - -static const u16 NCT6775_REG_TEMP_MON[] = { 0x73, 0x75, 0x77 }; - -static const u16 NCT6775_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { - 0, 0x152, 0x252, 0x628, 0x629, 0x62A }; -static const u16 NCT6775_REG_TEMP_HYST[ARRAY_SIZE(NCT6775_REG_TEMP)] = { - 0x3a, 0x153, 0x253, 0x673, 0x678, 0x67D }; -static const u16 NCT6775_REG_TEMP_OVER[ARRAY_SIZE(NCT6775_REG_TEMP)] = { - 0x39, 0x155, 0x255, 0x672, 0x677, 0x67C }; - -static const u16 NCT6775_REG_TEMP_SOURCE[ARRAY_SIZE(NCT6775_REG_TEMP)] = { - 0x621, 0x622, 0x623, 0x624, 0x625, 0x626 }; - -static const u16 NCT6775_REG_TEMP_SEL[] = { - 0x100, 0x200, 0x300, 0x800, 0x900, 0xa00, 0xb00 }; - -static const u16 NCT6775_REG_WEIGHT_TEMP_SEL[] = { - 0x139, 0x239, 0x339, 0x839, 0x939, 0xa39 }; -static const u16 NCT6775_REG_WEIGHT_TEMP_STEP[] = { - 0x13a, 0x23a, 0x33a, 0x83a, 0x93a, 0xa3a }; -static const u16 NCT6775_REG_WEIGHT_TEMP_STEP_TOL[] = { - 0x13b, 0x23b, 0x33b, 0x83b, 0x93b, 0xa3b }; -static const u16 NCT6775_REG_WEIGHT_DUTY_STEP[] = { - 0x13c, 0x23c, 0x33c, 0x83c, 0x93c, 0xa3c }; -static const u16 NCT6775_REG_WEIGHT_TEMP_BASE[] = { - 0x13d, 0x23d, 0x33d, 0x83d, 0x93d, 0xa3d }; - -static const u16 NCT6775_REG_TEMP_OFFSET[] = { 0x454, 0x455, 0x456 }; - -static const u16 NCT6775_REG_AUTO_TEMP[] = { - 0x121, 0x221, 0x321, 0x821, 0x921, 0xa21, 0xb21 }; -static const u16 NCT6775_REG_AUTO_PWM[] = { - 0x127, 0x227, 0x327, 0x827, 0x927, 0xa27, 0xb27 }; - -#define NCT6775_AUTO_TEMP(data, nr, p) ((data)->REG_AUTO_TEMP[nr] + (p)) -#define NCT6775_AUTO_PWM(data, nr, p) ((data)->REG_AUTO_PWM[nr] + (p)) - -static const u16 NCT6775_REG_CRITICAL_ENAB[] = { 0x134, 0x234, 0x334 }; - -static const u16 NCT6775_REG_CRITICAL_TEMP[] = { - 0x135, 0x235, 0x335, 0x835, 0x935, 0xa35, 0xb35 }; -static const u16 NCT6775_REG_CRITICAL_TEMP_TOLERANCE[] = { - 0x138, 0x238, 0x338, 0x838, 0x938, 0xa38, 0xb38 }; - -static const char *const nct6775_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN", - "AMD SB-TSI", - "PECI Agent 0", - "PECI Agent 1", - "PECI Agent 2", - "PECI Agent 3", - "PECI Agent 4", - "PECI Agent 5", - "PECI Agent 6", - "PECI Agent 7", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "PCH_DIM0_TEMP", - "PCH_DIM1_TEMP", - "PCH_DIM2_TEMP", - "PCH_DIM3_TEMP" -}; - -#define NCT6775_TEMP_MASK 0x001ffffe -#define NCT6775_VIRT_TEMP_MASK 0x00000000 - -static const u16 NCT6775_REG_TEMP_ALTERNATE[32] = { - [13] = 0x661, - [14] = 0x662, - [15] = 0x664, -}; - -static const u16 NCT6775_REG_TEMP_CRIT[32] = { - [4] = 0xa00, - [5] = 0xa01, - [6] = 0xa02, - [7] = 0xa03, - [8] = 0xa04, - [9] = 0xa05, - [10] = 0xa06, - [11] = 0xa07 -}; - -static const u16 NCT6775_REG_TSI_TEMP[] = { 0x669 }; - -/* NCT6776 specific data */ - -/* STEP_UP_TIME and STEP_DOWN_TIME regs are swapped for all chips but NCT6775 */ -#define NCT6776_REG_FAN_STEP_UP_TIME NCT6775_REG_FAN_STEP_DOWN_TIME -#define NCT6776_REG_FAN_STEP_DOWN_TIME NCT6775_REG_FAN_STEP_UP_TIME - -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 */ - -static const u16 NCT6776_REG_BEEP[NUM_REG_BEEP] = { 0xb2, 0xb3, 0xb4, 0xb5 }; - -static const s8 NCT6776_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ - 8, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - 24, /* global beep enable */ - 25, 26, 27, 28, 29, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 30, 31 }; /* intrusion0, intrusion1 */ - -static const u16 NCT6776_REG_TOLERANCE_H[] = { - 0x10c, 0x20c, 0x30c, 0x80c, 0x90c, 0xa0c, 0xb0c }; - -static const u8 NCT6776_REG_PWM_MODE[] = { 0x04, 0, 0, 0, 0, 0 }; -static const u8 NCT6776_PWM_MODE_MASK[] = { 0x01, 0, 0, 0, 0, 0 }; - -static const u16 NCT6776_REG_FAN_MIN[] = { - 0x63a, 0x63c, 0x63e, 0x640, 0x642, 0x64a, 0x64c }; -static const u16 NCT6776_REG_FAN_PULSES[NUM_FAN] = { - 0x644, 0x645, 0x646, 0x647, 0x648, 0x649 }; - -static const u16 NCT6776_REG_WEIGHT_DUTY_BASE[] = { - 0x13e, 0x23e, 0x33e, 0x83e, 0x93e, 0xa3e }; - -static const u16 NCT6776_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6775_REG_TEMP)] = { - 0x18, 0x152, 0x252, 0x628, 0x629, 0x62A }; - -static const char *const nct6776_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "SMBUSMASTER 2", - "SMBUSMASTER 3", - "SMBUSMASTER 4", - "SMBUSMASTER 5", - "SMBUSMASTER 6", - "SMBUSMASTER 7", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "PCH_DIM0_TEMP", - "PCH_DIM1_TEMP", - "PCH_DIM2_TEMP", - "PCH_DIM3_TEMP", - "BYTE_TEMP" -}; - -#define NCT6776_TEMP_MASK 0x007ffffe -#define NCT6776_VIRT_TEMP_MASK 0x00000000 - -static const u16 NCT6776_REG_TEMP_ALTERNATE[32] = { - [14] = 0x401, - [15] = 0x402, - [16] = 0x404, -}; - -static const u16 NCT6776_REG_TEMP_CRIT[32] = { - [11] = 0x709, - [12] = 0x70a, -}; - -static const u16 NCT6776_REG_TSI_TEMP[] = { - 0x409, 0x40b, 0x40d, 0x40f, 0x411, 0x413, 0x415, 0x417 }; - -/* 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 */ - -static const s8 NCT6779_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 6, 7, /* in0.. in7 */ - 8, 9, 10, 11, 12, 13, 14, /* in8..in14 */ - 24, /* global beep enable */ - 25, 26, 27, 28, 29, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, -1, -1, -1, -1, /* temp1..temp6 */ - 30, 31 }; /* intrusion0, intrusion1 */ - -static const u16 NCT6779_REG_FAN[] = { - 0x4c0, 0x4c2, 0x4c4, 0x4c6, 0x4c8, 0x4ca, 0x4ce }; -static const u16 NCT6779_REG_FAN_PULSES[NUM_FAN] = { - 0x644, 0x645, 0x646, 0x647, 0x648, 0x649, 0x64f }; - -static const u16 NCT6779_REG_CRITICAL_PWM_ENABLE[] = { - 0x136, 0x236, 0x336, 0x836, 0x936, 0xa36, 0xb36 }; -#define NCT6779_CRITICAL_PWM_ENABLE_MASK 0x01 -static const u16 NCT6779_REG_CRITICAL_PWM[] = { - 0x137, 0x237, 0x337, 0x837, 0x937, 0xa37, 0xb37 }; - -static const u16 NCT6779_REG_TEMP[] = { 0x27, 0x150 }; -static const u16 NCT6779_REG_TEMP_MON[] = { 0x73, 0x75, 0x77, 0x79, 0x7b }; -static const u16 NCT6779_REG_TEMP_CONFIG[ARRAY_SIZE(NCT6779_REG_TEMP)] = { - 0x18, 0x152 }; -static const u16 NCT6779_REG_TEMP_HYST[ARRAY_SIZE(NCT6779_REG_TEMP)] = { - 0x3a, 0x153 }; -static const u16 NCT6779_REG_TEMP_OVER[ARRAY_SIZE(NCT6779_REG_TEMP)] = { - 0x39, 0x155 }; - -static const u16 NCT6779_REG_TEMP_OFFSET[] = { - 0x454, 0x455, 0x456, 0x44a, 0x44b, 0x44c }; - -static const char *const nct6779_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "SMBUSMASTER 2", - "SMBUSMASTER 3", - "SMBUSMASTER 4", - "SMBUSMASTER 5", - "SMBUSMASTER 6", - "SMBUSMASTER 7", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "PCH_DIM0_TEMP", - "PCH_DIM1_TEMP", - "PCH_DIM2_TEMP", - "PCH_DIM3_TEMP", - "BYTE_TEMP", - "", - "", - "", - "", - "Virtual_TEMP" -}; - -#define NCT6779_TEMP_MASK 0x07ffff7e -#define NCT6779_VIRT_TEMP_MASK 0x00000000 -#define NCT6791_TEMP_MASK 0x87ffff7e -#define NCT6791_VIRT_TEMP_MASK 0x80000000 - -static const u16 NCT6779_REG_TEMP_ALTERNATE[32] - = { 0x490, 0x491, 0x492, 0x493, 0x494, 0x495, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0x400, 0x401, 0x402, 0x404, 0x405, 0x406, 0x407, - 0x408, 0 }; - -static const u16 NCT6779_REG_TEMP_CRIT[32] = { - [15] = 0x709, - [16] = 0x70a, -}; - -/* NCT6791 specific data */ - -#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 - -static const u16 NCT6791_REG_WEIGHT_TEMP_SEL[NUM_FAN] = { 0, 0x239 }; -static const u16 NCT6791_REG_WEIGHT_TEMP_STEP[NUM_FAN] = { 0, 0x23a }; -static const u16 NCT6791_REG_WEIGHT_TEMP_STEP_TOL[NUM_FAN] = { 0, 0x23b }; -static const u16 NCT6791_REG_WEIGHT_DUTY_STEP[NUM_FAN] = { 0, 0x23c }; -static const u16 NCT6791_REG_WEIGHT_TEMP_BASE[NUM_FAN] = { 0, 0x23d }; -static const u16 NCT6791_REG_WEIGHT_DUTY_BASE[NUM_FAN] = { 0, 0x23e }; - -static const u16 NCT6791_REG_ALARM[NUM_REG_ALARM] = { - 0x459, 0x45A, 0x45B, 0x568, 0x45D }; - -static const s8 NCT6791_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, 33, /* fan1..fan6 */ - -1, -1, /* unused */ - 4, 5, 13, -1, -1, -1, /* temp1..temp6 */ - 12, 9 }; /* intrusion0, intrusion1 */ - -/* NCT6792/NCT6793 specific data */ - -static const u16 NCT6792_REG_TEMP_MON[] = { - 0x73, 0x75, 0x77, 0x79, 0x7b, 0x7d }; -static const u16 NCT6792_REG_BEEP[NUM_REG_BEEP] = { - 0xb2, 0xb3, 0xb4, 0xb5, 0xbf }; - -static const char *const nct6792_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "SMBUSMASTER 2", - "SMBUSMASTER 3", - "SMBUSMASTER 4", - "SMBUSMASTER 5", - "SMBUSMASTER 6", - "SMBUSMASTER 7", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "PCH_DIM0_TEMP", - "PCH_DIM1_TEMP", - "PCH_DIM2_TEMP", - "PCH_DIM3_TEMP", - "BYTE_TEMP", - "PECI Agent 0 Calibration", - "PECI Agent 1 Calibration", - "", - "", - "Virtual_TEMP" -}; - -#define NCT6792_TEMP_MASK 0x9fffff7e -#define NCT6792_VIRT_TEMP_MASK 0x80000000 - -static const char *const nct6793_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "", - "", - "", - "", - "", - "", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "Agent0 Dimm0 ", - "Agent0 Dimm1", - "Agent1 Dimm0", - "Agent1 Dimm1", - "BYTE_TEMP0", - "BYTE_TEMP1", - "PECI Agent 0 Calibration", - "PECI Agent 1 Calibration", - "", - "Virtual_TEMP" -}; - -#define NCT6793_TEMP_MASK 0xbfff037e -#define NCT6793_VIRT_TEMP_MASK 0x80000000 - -static const char *const nct6795_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "SMBUSMASTER 2", - "SMBUSMASTER 3", - "SMBUSMASTER 4", - "SMBUSMASTER 5", - "SMBUSMASTER 6", - "SMBUSMASTER 7", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "Agent0 Dimm0", - "Agent0 Dimm1", - "Agent1 Dimm0", - "Agent1 Dimm1", - "BYTE_TEMP0", - "BYTE_TEMP1", - "PECI Agent 0 Calibration", - "PECI Agent 1 Calibration", - "", - "Virtual_TEMP" -}; - -#define NCT6795_TEMP_MASK 0xbfffff7e -#define NCT6795_VIRT_TEMP_MASK 0x80000000 - -static const char *const nct6796_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "AUXTIN4", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "Virtual_TEMP", - "Virtual_TEMP", - "", - "", - "", - "", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "Agent0 Dimm0", - "Agent0 Dimm1", - "Agent1 Dimm0", - "Agent1 Dimm1", - "BYTE_TEMP0", - "BYTE_TEMP1", - "PECI Agent 0 Calibration", - "PECI Agent 1 Calibration", - "", - "Virtual_TEMP" -}; - -#define NCT6796_TEMP_MASK 0xbfff0ffe -#define NCT6796_VIRT_TEMP_MASK 0x80000c00 - -static const u16 NCT6796_REG_TSI_TEMP[] = { 0x409, 0x40b }; - -static const char *const nct6798_temp_label[] = { - "", - "SYSTIN", - "CPUTIN", - "AUXTIN0", - "AUXTIN1", - "AUXTIN2", - "AUXTIN3", - "AUXTIN4", - "SMBUSMASTER 0", - "SMBUSMASTER 1", - "Virtual_TEMP", - "Virtual_TEMP", - "", - "", - "", - "", - "PECI Agent 0", - "PECI Agent 1", - "PCH_CHIP_CPU_MAX_TEMP", - "PCH_CHIP_TEMP", - "PCH_CPU_TEMP", - "PCH_MCH_TEMP", - "Agent0 Dimm0", - "Agent0 Dimm1", - "Agent1 Dimm0", - "Agent1 Dimm1", - "BYTE_TEMP0", - "BYTE_TEMP1", - "PECI Agent 0 Calibration", /* undocumented */ - "PECI Agent 1 Calibration", /* undocumented */ - "", - "Virtual_TEMP" -}; - -#define NCT6798_TEMP_MASK 0xbfff0ffe -#define NCT6798_VIRT_TEMP_MASK 0x80000c00 - -/* NCT6102D/NCT6106D specific data */ - -#define NCT6106_REG_VBAT 0x318 -#define NCT6106_REG_DIODE 0x319 -#define NCT6106_DIODE_MASK 0x01 - -static const u16 NCT6106_REG_IN_MAX[] = { - 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9e, 0xa0, 0xa2 }; -static const u16 NCT6106_REG_IN_MIN[] = { - 0x91, 0x93, 0x95, 0x97, 0x99, 0x9b, 0x9f, 0xa1, 0xa3 }; -static const u16 NCT6106_REG_IN[] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x07, 0x08, 0x09 }; - -static const u16 NCT6106_REG_TEMP[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; -static const u16 NCT6106_REG_TEMP_MON[] = { 0x18, 0x19, 0x1a }; -static const u16 NCT6106_REG_TEMP_HYST[] = { - 0xc3, 0xc7, 0xcb, 0xcf, 0xd3, 0xd7 }; -static const u16 NCT6106_REG_TEMP_OVER[] = { - 0xc2, 0xc6, 0xca, 0xce, 0xd2, 0xd6 }; -static const u16 NCT6106_REG_TEMP_CRIT_L[] = { - 0xc0, 0xc4, 0xc8, 0xcc, 0xd0, 0xd4 }; -static const u16 NCT6106_REG_TEMP_CRIT_H[] = { - 0xc1, 0xc5, 0xc9, 0xcf, 0xd1, 0xd5 }; -static const u16 NCT6106_REG_TEMP_OFFSET[] = { 0x311, 0x312, 0x313 }; -static const u16 NCT6106_REG_TEMP_CONFIG[] = { - 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc }; - -static const u16 NCT6106_REG_FAN[] = { 0x20, 0x22, 0x24 }; -static const u16 NCT6106_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4 }; -static const u16 NCT6106_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6 }; -static const u16 NCT6106_FAN_PULSE_SHIFT[] = { 0, 2, 4 }; - -static const u8 NCT6106_REG_PWM_MODE[] = { 0xf3, 0xf3, 0xf3 }; -static const u8 NCT6106_PWM_MODE_MASK[] = { 0x01, 0x02, 0x04 }; -static const u16 NCT6106_REG_PWM_READ[] = { 0x4a, 0x4b, 0x4c }; -static const u16 NCT6106_REG_FAN_MODE[] = { 0x113, 0x123, 0x133 }; -static const u16 NCT6106_REG_TEMP_SOURCE[] = { - 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5 }; - -static const u16 NCT6106_REG_CRITICAL_TEMP[] = { 0x11a, 0x12a, 0x13a }; -static const u16 NCT6106_REG_CRITICAL_TEMP_TOLERANCE[] = { - 0x11b, 0x12b, 0x13b }; - -static const u16 NCT6106_REG_CRITICAL_PWM_ENABLE[] = { 0x11c, 0x12c, 0x13c }; -#define NCT6106_CRITICAL_PWM_ENABLE_MASK 0x10 -static const u16 NCT6106_REG_CRITICAL_PWM[] = { 0x11d, 0x12d, 0x13d }; - -static const u16 NCT6106_REG_FAN_STEP_UP_TIME[] = { 0x114, 0x124, 0x134 }; -static const u16 NCT6106_REG_FAN_STEP_DOWN_TIME[] = { 0x115, 0x125, 0x135 }; -static const u16 NCT6106_REG_FAN_STOP_OUTPUT[] = { 0x116, 0x126, 0x136 }; -static const u16 NCT6106_REG_FAN_START_OUTPUT[] = { 0x117, 0x127, 0x137 }; -static const u16 NCT6106_REG_FAN_STOP_TIME[] = { 0x118, 0x128, 0x138 }; -static const u16 NCT6106_REG_TOLERANCE_H[] = { 0x112, 0x122, 0x132 }; - -static const u16 NCT6106_REG_TARGET[] = { 0x111, 0x121, 0x131 }; - -static const u16 NCT6106_REG_WEIGHT_TEMP_SEL[] = { 0x168, 0x178, 0x188 }; -static const u16 NCT6106_REG_WEIGHT_TEMP_STEP[] = { 0x169, 0x179, 0x189 }; -static const u16 NCT6106_REG_WEIGHT_TEMP_STEP_TOL[] = { 0x16a, 0x17a, 0x18a }; -static const u16 NCT6106_REG_WEIGHT_DUTY_STEP[] = { 0x16b, 0x17b, 0x18b }; -static const u16 NCT6106_REG_WEIGHT_TEMP_BASE[] = { 0x16c, 0x17c, 0x18c }; -static const u16 NCT6106_REG_WEIGHT_DUTY_BASE[] = { 0x16d, 0x17d, 0x18d }; - -static const u16 NCT6106_REG_AUTO_TEMP[] = { 0x160, 0x170, 0x180 }; -static const u16 NCT6106_REG_AUTO_PWM[] = { 0x164, 0x174, 0x184 }; - -static const u16 NCT6106_REG_ALARM[NUM_REG_ALARM] = { - 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d }; - -static const s8 NCT6106_ALARM_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, -1, -1, -1, -1, -1, -1, /* in8..in14 */ - -1, /* unused */ - 32, 33, 34, -1, -1, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 48, -1 /* intrusion0, intrusion1 */ -}; - -static const u16 NCT6106_REG_BEEP[NUM_REG_BEEP] = { - 0x3c0, 0x3c1, 0x3c2, 0x3c3, 0x3c4 }; - -static const s8 NCT6106_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ - 32, /* global beep enable */ - 24, 25, 26, 27, 28, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, 19, 20, 21, /* temp1..temp6 */ - 34, -1 /* intrusion0, intrusion1 */ -}; - -static const u16 NCT6106_REG_TEMP_ALTERNATE[32] = { - [14] = 0x51, - [15] = 0x52, - [16] = 0x54, -}; - -static const u16 NCT6106_REG_TEMP_CRIT[32] = { - [11] = 0x204, - [12] = 0x205, -}; - -static const u16 NCT6106_REG_TSI_TEMP[] = { 0x59, 0x5b, 0x5d, 0x5f, 0x61, 0x63, 0x65, 0x67 }; - -/* NCT6112D/NCT6114D/NCT6116D specific data */ - -static const u16 NCT6116_REG_FAN[] = { 0x20, 0x22, 0x24, 0x26, 0x28 }; -static const u16 NCT6116_REG_FAN_MIN[] = { 0xe0, 0xe2, 0xe4, 0xe6, 0xe8 }; -static const u16 NCT6116_REG_FAN_PULSES[] = { 0xf6, 0xf6, 0xf6, 0xf6, 0xf5 }; -static const u16 NCT6116_FAN_PULSE_SHIFT[] = { 0, 2, 4, 6, 6 }; - -static const u16 NCT6116_REG_PWM[] = { 0x119, 0x129, 0x139, 0x199, 0x1a9 }; -static const u16 NCT6116_REG_FAN_MODE[] = { 0x113, 0x123, 0x133, 0x193, 0x1a3 }; -static const u16 NCT6116_REG_TEMP_SEL[] = { 0x110, 0x120, 0x130, 0x190, 0x1a0 }; -static const u16 NCT6116_REG_TEMP_SOURCE[] = { - 0xb0, 0xb1, 0xb2 }; - -static const u16 NCT6116_REG_CRITICAL_TEMP[] = { - 0x11a, 0x12a, 0x13a, 0x19a, 0x1aa }; -static const u16 NCT6116_REG_CRITICAL_TEMP_TOLERANCE[] = { - 0x11b, 0x12b, 0x13b, 0x19b, 0x1ab }; - -static const u16 NCT6116_REG_CRITICAL_PWM_ENABLE[] = { - 0x11c, 0x12c, 0x13c, 0x19c, 0x1ac }; -static const u16 NCT6116_REG_CRITICAL_PWM[] = { - 0x11d, 0x12d, 0x13d, 0x19d, 0x1ad }; - -static const u16 NCT6116_REG_FAN_STEP_UP_TIME[] = { - 0x114, 0x124, 0x134, 0x194, 0x1a4 }; -static const u16 NCT6116_REG_FAN_STEP_DOWN_TIME[] = { - 0x115, 0x125, 0x135, 0x195, 0x1a5 }; -static const u16 NCT6116_REG_FAN_STOP_OUTPUT[] = { - 0x116, 0x126, 0x136, 0x196, 0x1a6 }; -static const u16 NCT6116_REG_FAN_START_OUTPUT[] = { - 0x117, 0x127, 0x137, 0x197, 0x1a7 }; -static const u16 NCT6116_REG_FAN_STOP_TIME[] = { - 0x118, 0x128, 0x138, 0x198, 0x1a8 }; -static const u16 NCT6116_REG_TOLERANCE_H[] = { - 0x112, 0x122, 0x132, 0x192, 0x1a2 }; - -static const u16 NCT6116_REG_TARGET[] = { - 0x111, 0x121, 0x131, 0x191, 0x1a1 }; - -static const u16 NCT6116_REG_AUTO_TEMP[] = { - 0x160, 0x170, 0x180, 0x1d0, 0x1e0 }; -static const u16 NCT6116_REG_AUTO_PWM[] = { - 0x164, 0x174, 0x184, 0x1d4, 0x1e4 }; - -static const s8 NCT6116_ALARM_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, -1, -1, -1, -1, -1, -1, /* in8..in9 */ - -1, /* unused */ - 32, 33, 34, 35, 36, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ - 48, -1 /* intrusion0, intrusion1 */ -}; - -static const s8 NCT6116_BEEP_BITS[] = { - 0, 1, 2, 3, 4, 5, 7, 8, /* in0.. in7 */ - 9, 10, 11, 12, -1, -1, -1, /* in8..in14 */ - 32, /* global beep enable */ - 24, 25, 26, 27, 28, /* fan1..fan5 */ - -1, -1, -1, /* unused */ - 16, 17, 18, -1, -1, -1, /* temp1..temp6 */ - 34, -1 /* intrusion0, intrusion1 */ -}; - -static const u16 NCT6116_REG_TSI_TEMP[] = { 0x59, 0x5b }; - -static enum pwm_enable reg_to_pwm_enable(int pwm, int mode) -{ - if (mode == 0 && pwm == 255) - return off; - return mode + 1; -} - -static int pwm_enable_to_reg(enum pwm_enable mode) -{ - if (mode == off) - return 0; - return mode - 1; -} - -/* - * Conversions - */ - -/* 1 is DC mode, output in ms */ -static unsigned int step_time_from_reg(u8 reg, u8 mode) -{ - return mode ? 400 * reg : 100 * reg; -} - -static u8 step_time_to_reg(unsigned int msec, u8 mode) -{ - return clamp_val((mode ? (msec + 200) / 400 : - (msec + 50) / 100), 1, 255); -} - -static unsigned int fan_from_reg8(u16 reg, unsigned int divreg) -{ - if (reg == 0 || reg == 255) - return 0; - return 1350000U / (reg << divreg); -} - -static unsigned int fan_from_reg13(u16 reg, unsigned int divreg) -{ - if ((reg & 0xff1f) == 0xff1f) - return 0; - - reg = (reg & 0x1f) | ((reg & 0xff00) >> 3); - - if (reg == 0) - return 0; - - return 1350000U / reg; -} - -static unsigned int fan_from_reg16(u16 reg, unsigned int divreg) -{ - if (reg == 0 || reg == 0xffff) - return 0; - - /* - * Even though the registers are 16 bit wide, the fan divisor - * still applies. - */ - return 1350000U / (reg << divreg); -} - -static unsigned int fan_from_reg_rpm(u16 reg, unsigned int divreg) -{ - return reg; -} - -static u16 fan_to_reg(u32 fan, unsigned int divreg) -{ - if (!fan) - return 0; - - return (1350000U / fan) >> divreg; -} - -static inline unsigned int -div_from_reg(u8 reg) -{ - return BIT(reg); -} - -/* - * 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); -} - -/* TSI temperatures are in 8.3 format */ -static inline unsigned int tsi_temp_from_reg(unsigned int reg) -{ - return (reg >> 5) * 125; -} - -/* - * Data structures and manipulation thereof - */ - -struct nct6775_data { - int addr; /* IO base of hw monitor block */ - struct nct6775_sio_data *sio_data; - enum kinds kind; - const char *name; - - const struct attribute_group *groups[7]; - u8 num_groups; - - u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, - * 3=temp_crit, 4=temp_lcrit - */ - u8 temp_src[NUM_TEMP]; - u16 reg_temp_config[NUM_TEMP]; - const char * const *temp_label; - u32 temp_mask; - u32 virt_temp_mask; - - u16 REG_CONFIG; - u16 REG_VBAT; - u16 REG_DIODE; - u8 DIODE_MASK; - - const s8 *ALARM_BITS; - const s8 *BEEP_BITS; - - const u16 *REG_VIN; - const u16 *REG_IN_MINMAX[2]; - - const u16 *REG_TARGET; - const u16 *REG_FAN; - const u16 *REG_FAN_MODE; - const u16 *REG_FAN_MIN; - const u16 *REG_FAN_PULSES; - const u16 *FAN_PULSE_SHIFT; - const u16 *REG_FAN_TIME[3]; - - const u16 *REG_TOLERANCE_H; - - const u8 *REG_PWM_MODE; - const u8 *PWM_MODE_MASK; - - const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step, - * [5]=weight_duty_step, [6]=weight_duty_base - */ - const u16 *REG_PWM_READ; - - const u16 *REG_CRITICAL_PWM_ENABLE; - u8 CRITICAL_PWM_ENABLE_MASK; - const u16 *REG_CRITICAL_PWM; - - const u16 *REG_AUTO_TEMP; - const u16 *REG_AUTO_PWM; - - const u16 *REG_CRITICAL_TEMP; - const u16 *REG_CRITICAL_TEMP_TOLERANCE; - - const u16 *REG_TEMP_SOURCE; /* temp register sources */ - const u16 *REG_TEMP_SEL; - const u16 *REG_WEIGHT_TEMP_SEL; - const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ - - const u16 *REG_TEMP_OFFSET; - - const u16 *REG_ALARM; - const u16 *REG_BEEP; - - const u16 *REG_TSI_TEMP; - - unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); - unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); - - 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 */ - unsigned int rpm[NUM_FAN]; - u16 fan_min[NUM_FAN]; - u8 fan_pulses[NUM_FAN]; - u8 fan_div[NUM_FAN]; - u8 has_pwm; - u8 has_fan; /* some fan inputs can be disabled */ - u8 has_fan_min; /* some fans don't have min register */ - bool has_fan_div; - - u8 num_temp_alarms; /* 2, 3, or 6 */ - u8 num_temp_beeps; /* 2, 3, or 6 */ - u8 temp_fixed_num; /* 3 or 6 */ - u8 temp_type[NUM_TEMP_FIXED]; - s8 temp_offset[NUM_TEMP_FIXED]; - s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, - * 3=temp_crit, 4=temp_lcrit */ - s16 tsi_temp[NUM_TSI_TEMP]; - u64 alarms; - u64 beeps; - - u8 pwm_num; /* number of pwm */ - u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage, - * 1->PWM variable duty cycle - */ - enum pwm_enable pwm_enable[NUM_FAN]; - /* 0->off - * 1->manual - * 2->thermal cruise mode (also called SmartFan I) - * 3->fan speed cruise mode - * 4->SmartFan III - * 5->enhanced variable thermal cruise (SmartFan IV) - */ - u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, - * [3]=pwm_max, [4]=pwm_step, - * [5]=weight_duty_step, [6]=weight_duty_base - */ - - u8 target_temp[NUM_FAN]; - u8 target_temp_mask; - u32 target_speed[NUM_FAN]; - u32 target_speed_tolerance[NUM_FAN]; - u8 speed_tolerance_limit; - - u8 temp_tolerance[2][NUM_FAN]; - u8 tolerance_mask; - - u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ - - /* Automatic fan speed control registers */ - int auto_pwm_num; - u8 auto_pwm[NUM_FAN][7]; - u8 auto_temp[NUM_FAN][7]; - u8 pwm_temp_sel[NUM_FAN]; - u8 pwm_weight_temp_sel[NUM_FAN]; - u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol, - * 2->temp_base - */ - - u8 vid; - u8 vrm; - - bool have_vid; - - u16 have_temp; - u16 have_temp_fixed; - u16 have_tsi_temp; - u16 have_in; - - /* Remember extra register values over suspend/resume */ - u8 vbat; - u8 fandiv1; - u8 fandiv2; - u8 sio_reg_enable; - - struct regmap *regmap; - bool read_only; -}; - -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 inline umode_t nct6775_attr_mode(struct nct6775_data *data, struct attribute *attr) -{ - return data->read_only ? (attr->mode & ~0222) : attr->mode; -} - -static int nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group) -{ - /* Need to leave a NULL terminator at the end of data->groups */ - if (data->num_groups == ARRAY_SIZE(data->groups) - 1) - return -ENOBUFS; - - data->groups[data->num_groups++] = group; - return 0; -} - -static int nct6775_add_template_attr_group(struct device *dev, struct nct6775_data *data, - const struct sensor_template_group *tg, int repeat) -{ - struct attribute_group *group; - struct sensor_device_attr_u *su; - struct sensor_device_attribute *a; - struct sensor_device_attribute_2 *a2; - struct attribute **attrs; - struct sensor_device_template **t; - int i, count; - - if (repeat <= 0) - return -EINVAL; - - t = tg->templates; - for (count = 0; *t; t++, count++) - ; - - if (count == 0) - return -EINVAL; - - group = devm_kzalloc(dev, sizeof(*group), GFP_KERNEL); - if (group == NULL) - return -ENOMEM; - - attrs = devm_kcalloc(dev, repeat * count + 1, sizeof(*attrs), - GFP_KERNEL); - if (attrs == NULL) - return -ENOMEM; - - su = devm_kzalloc(dev, array3_size(repeat, count, sizeof(*su)), - GFP_KERNEL); - if (su == NULL) - return -ENOMEM; - - group->attrs = attrs; - group->is_visible = tg->is_visible; - - for (i = 0; i < repeat; i++) { - t = tg->templates; - while (*t != NULL) { - 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 nct6775_add_attr_group(data, group); -} - -static bool is_word_sized(struct nct6775_data *data, u16 reg) -{ - switch (data->kind) { - case nct6106: - return reg == 0x20 || reg == 0x22 || reg == 0x24 || - (reg >= 0x59 && reg < 0x69 && (reg & 1)) || - reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || - reg == 0x111 || reg == 0x121 || reg == 0x131; - case nct6116: - return reg == 0x20 || reg == 0x22 || reg == 0x24 || - reg == 0x26 || reg == 0x28 || reg == 0x59 || reg == 0x5b || - reg == 0xe0 || reg == 0xe2 || reg == 0xe4 || reg == 0xe6 || - reg == 0xe8 || reg == 0x111 || reg == 0x121 || reg == 0x131 || - reg == 0x191 || reg == 0x1a1; - 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 == 0x669 || - ((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 >= 0x409 && reg < 0x419 && (reg & 1)) || - reg == 0x640 || reg == 0x642 || - ((reg & 0xfff0) == 0x650 && (reg & 0x000f) >= 0x06) || - reg == 0x73 || reg == 0x75 || reg == 0x77; - case nct6779: - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - return reg == 0x150 || reg == 0x153 || reg == 0x155 || - (reg & 0xfff0) == 0x4c0 || - reg == 0x402 || - (reg >= 0x409 && reg < 0x419 && (reg & 1)) || - reg == 0x63a || reg == 0x63c || reg == 0x63e || - reg == 0x640 || reg == 0x642 || reg == 0x64a || - reg == 0x64c || - reg == 0x73 || reg == 0x75 || reg == 0x77 || reg == 0x79 || - reg == 0x7b || reg == 0x7d; - } - return false; -} - -static inline void nct6775_wmi_set_bank(struct nct6775_data *data, u16 reg) -{ - u8 bank = reg >> 8; - - data->bank = bank; -} - -static int nct6775_wmi_reg_read(void *ctx, unsigned int reg, unsigned int *val) -{ - struct nct6775_data *data = ctx; - int err, word_sized = is_word_sized(data, reg); - u8 tmp = 0; - u16 res; - - nct6775_wmi_set_bank(data, reg); - - err = nct6775_asuswmi_read(data->bank, reg & 0xff, &tmp); - if (err) - return err; - - res = tmp; - if (word_sized) { - err = nct6775_asuswmi_read(data->bank, (reg & 0xff) + 1, &tmp); - if (err) - return err; - - res = (res << 8) + tmp; - } - *val = res; - return 0; -} - -static inline int nct6775_read_value(struct nct6775_data *data, u16 reg, u16 *value) -{ - unsigned int tmp; - int ret = regmap_read(data->regmap, reg, &tmp); - - if (!ret) - *value = tmp; - return ret; -} - -static int nct6775_wmi_reg_write(void *ctx, unsigned int reg, unsigned int value) -{ - struct nct6775_data *data = ctx; - int res, word_sized = is_word_sized(data, reg); - - nct6775_wmi_set_bank(data, reg); - - if (word_sized) { - res = nct6775_asuswmi_write(data->bank, reg & 0xff, value >> 8); - if (res) - return res; - - res = nct6775_asuswmi_write(data->bank, (reg & 0xff) + 1, value); - } else { - res = nct6775_asuswmi_write(data->bank, reg & 0xff, value); - } - - return res; -} - -static inline int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) -{ - return regmap_write(data->regmap, reg, value); -} - -/* - * 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 int nct6775_reg_read(void *ctx, unsigned int reg, unsigned int *val) -{ - struct nct6775_data *data = ctx; - int word_sized = is_word_sized(data, reg); - - nct6775_set_bank(data, reg); - outb_p(reg & 0xff, data->addr + ADDR_REG_OFFSET); - *val = inb_p(data->addr + DATA_REG_OFFSET); - if (word_sized) { - outb_p((reg & 0xff) + 1, - data->addr + ADDR_REG_OFFSET); - *val = (*val << 8) + inb_p(data->addr + DATA_REG_OFFSET); - } - return 0; -} - -static int nct6775_reg_write(void *ctx, unsigned int reg, unsigned int value) -{ - struct nct6775_data *data = ctx; - int word_sized = is_word_sized(data, reg); - - 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); - return 0; -} - -/* We left-align 8-bit temperature values to make the code simpler */ -static int nct6775_read_temp(struct nct6775_data *data, u16 reg, u16 *val) -{ - int err; - - err = nct6775_read_value(data, reg, val); - if (err) - return err; - - if (!is_word_sized(data, reg)) - *val <<= 8; - - return 0; -} - -static int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) -{ - if (!is_word_sized(data, reg)) - value >>= 8; - return nct6775_write_value(data, reg, value); -} - -/* This function assumes that the caller holds data->update_lock */ -static int nct6775_write_fan_div(struct nct6775_data *data, int nr) -{ - u16 reg; - int err; - u16 fandiv_reg = nr < 2 ? NCT6775_REG_FANDIV1 : NCT6775_REG_FANDIV2; - unsigned int oddshift = (nr & 1) * 4; /* masks shift by four if nr is odd */ - - err = nct6775_read_value(data, fandiv_reg, ®); - if (err) - return err; - reg &= 0x70 >> oddshift; - reg |= data->fan_div[nr] & (0x7 << oddshift); - return nct6775_write_value(data, fandiv_reg, reg); -} - -static int nct6775_write_fan_div_common(struct nct6775_data *data, int nr) -{ - if (data->kind == nct6775) - return nct6775_write_fan_div(data, nr); - return 0; -} - -static int nct6775_update_fan_div(struct nct6775_data *data) -{ - int err; - u16 i; - - err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &i); - if (err) - return err; - data->fan_div[0] = i & 0x7; - data->fan_div[1] = (i & 0x70) >> 4; - err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &i); - if (err) - return err; - data->fan_div[2] = i & 0x7; - if (data->has_fan & BIT(3)) - data->fan_div[3] = (i & 0x70) >> 4; - - return 0; -} - -static int nct6775_update_fan_div_common(struct nct6775_data *data) -{ - if (data->kind == nct6775) - return nct6775_update_fan_div(data); - return 0; -} - -static int nct6775_init_fan_div(struct nct6775_data *data) -{ - int i, err; - - err = nct6775_update_fan_div_common(data); - if (err) - return err; - - /* - * For all fans, start with highest divider value if the divider - * register is not initialized. This ensures that we get a - * reading from the fan count register, even if it is not optimal. - * We'll compute a better divider later on. - */ - for (i = 0; i < ARRAY_SIZE(data->fan_div); i++) { - if (!(data->has_fan & BIT(i))) - continue; - if (data->fan_div[i] == 0) { - data->fan_div[i] = 7; - err = nct6775_write_fan_div_common(data, i); - if (err) - return err; - } - } - - return 0; -} - -static int nct6775_init_fan_common(struct device *dev, - struct nct6775_data *data) -{ - int i, err; - u16 reg; - - if (data->has_fan_div) { - err = nct6775_init_fan_div(data); - if (err) - return err; - } - - /* - * If fan_min is not set (0), set it to 0xff to disable it. This - * prevents the unnecessary warning when fanX_min is reported as 0. - */ - for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { - if (data->has_fan_min & BIT(i)) { - err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); - if (err) - return err; - if (!reg) { - err = nct6775_write_value(data, data->REG_FAN_MIN[i], - data->has_fan_div ? 0xff : 0xff1f); - if (err) - return err; - } - } - } - - return 0; -} - -static int nct6775_select_fan_div(struct device *dev, - struct nct6775_data *data, int nr, u16 reg) -{ - int err; - u8 fan_div = data->fan_div[nr]; - u16 fan_min; - - if (!data->has_fan_div) - return 0; - - /* - * If we failed to measure the fan speed, or the reported value is not - * in the optimal range, and the clock divider can be modified, - * let's try that for next time. - */ - if (reg == 0x00 && fan_div < 0x07) - fan_div++; - else if (reg != 0x00 && reg < 0x30 && fan_div > 0) - fan_div--; - - if (fan_div != data->fan_div[nr]) { - dev_dbg(dev, "Modifying fan%d clock divider from %u to %u\n", - nr + 1, div_from_reg(data->fan_div[nr]), - div_from_reg(fan_div)); - - /* Preserve min limit if possible */ - if (data->has_fan_min & BIT(nr)) { - fan_min = data->fan_min[nr]; - if (fan_div > data->fan_div[nr]) { - if (fan_min != 255 && fan_min > 1) - fan_min >>= 1; - } else { - if (fan_min != 255) { - fan_min <<= 1; - if (fan_min > 254) - fan_min = 254; - } - } - if (fan_min != data->fan_min[nr]) { - data->fan_min[nr] = fan_min; - err = nct6775_write_value(data, data->REG_FAN_MIN[nr], fan_min); - if (err) - return err; - } - } - data->fan_div[nr] = fan_div; - err = nct6775_write_fan_div_common(data, nr); - if (err) - return err; - } - - return 0; -} - -static int nct6775_update_pwm(struct device *dev) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - int i, j, err; - u16 fanmodecfg, reg; - bool duty_is_dc; - - for (i = 0; i < data->pwm_num; i++) { - if (!(data->has_pwm & BIT(i))) - continue; - - err = nct6775_read_value(data, data->REG_PWM_MODE[i], ®); - if (err) - return err; - duty_is_dc = data->REG_PWM_MODE[i] && (reg & data->PWM_MODE_MASK[i]); - data->pwm_mode[i] = !duty_is_dc; - - err = nct6775_read_value(data, data->REG_FAN_MODE[i], &fanmodecfg); - if (err) - return err; - for (j = 0; j < ARRAY_SIZE(data->REG_PWM); j++) { - if (data->REG_PWM[j] && data->REG_PWM[j][i]) { - err = nct6775_read_value(data, data->REG_PWM[j][i], ®); - if (err) - return err; - data->pwm[j][i] = reg; - } - } - - data->pwm_enable[i] = reg_to_pwm_enable(data->pwm[0][i], - (fanmodecfg >> 4) & 7); - - if (!data->temp_tolerance[0][i] || - data->pwm_enable[i] != speed_cruise) - data->temp_tolerance[0][i] = fanmodecfg & 0x0f; - if (!data->target_speed_tolerance[i] || - data->pwm_enable[i] == speed_cruise) { - u8 t = fanmodecfg & 0x0f; - - if (data->REG_TOLERANCE_H) { - err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); - if (err) - return err; - t |= (reg & 0x70) >> 1; - } - data->target_speed_tolerance[i] = t; - } - - err = nct6775_read_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[i], ®); - if (err) - return err; - data->temp_tolerance[1][i] = reg; - - err = nct6775_read_value(data, data->REG_TEMP_SEL[i], ®); - if (err) - return err; - data->pwm_temp_sel[i] = reg & 0x1f; - /* If fan can stop, report floor as 0 */ - if (reg & 0x80) - data->pwm[2][i] = 0; - - if (!data->REG_WEIGHT_TEMP_SEL[i]) - continue; - - err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[i], ®); - if (err) - return err; - data->pwm_weight_temp_sel[i] = reg & 0x1f; - /* If weight is disabled, report weight source as 0 */ - if (!(reg & 0x80)) - data->pwm_weight_temp_sel[i] = 0; - - /* Weight temp data */ - for (j = 0; j < ARRAY_SIZE(data->weight_temp); j++) { - err = nct6775_read_value(data, data->REG_WEIGHT_TEMP[j][i], ®); - if (err) - return err; - data->weight_temp[j][i] = reg; - } - } - - return 0; -} - -static int nct6775_update_pwm_limits(struct device *dev) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - int i, j, err; - u16 reg, reg_t; - - for (i = 0; i < data->pwm_num; i++) { - if (!(data->has_pwm & BIT(i))) - continue; - - for (j = 0; j < ARRAY_SIZE(data->fan_time); j++) { - err = nct6775_read_value(data, data->REG_FAN_TIME[j][i], ®); - if (err) - return err; - data->fan_time[j][i] = reg; - } - - err = nct6775_read_value(data, data->REG_TARGET[i], ®_t); - if (err) - return err; - - /* Update only in matching mode or if never updated */ - if (!data->target_temp[i] || - data->pwm_enable[i] == thermal_cruise) - data->target_temp[i] = reg_t & data->target_temp_mask; - if (!data->target_speed[i] || - data->pwm_enable[i] == speed_cruise) { - if (data->REG_TOLERANCE_H) { - err = nct6775_read_value(data, data->REG_TOLERANCE_H[i], ®); - if (err) - return err; - reg_t |= (reg & 0x0f) << 8; - } - data->target_speed[i] = reg_t; - } - - for (j = 0; j < data->auto_pwm_num; j++) { - err = nct6775_read_value(data, NCT6775_AUTO_PWM(data, i, j), ®); - if (err) - return err; - data->auto_pwm[i][j] = reg; - - err = nct6775_read_value(data, NCT6775_AUTO_TEMP(data, i, j), ®); - if (err) - return err; - data->auto_temp[i][j] = reg; - } - - /* critical auto_pwm temperature data */ - err = nct6775_read_value(data, data->REG_CRITICAL_TEMP[i], ®); - if (err) - return err; - data->auto_temp[i][data->auto_pwm_num] = reg; - - switch (data->kind) { - case nct6775: - err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[i], ®); - if (err) - return err; - data->auto_pwm[i][data->auto_pwm_num] = - (reg & 0x02) ? 0xff : 0x00; - break; - case nct6776: - data->auto_pwm[i][data->auto_pwm_num] = 0xff; - break; - case nct6106: - case nct6116: - case nct6779: - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[i], ®); - if (err) - return err; - if (reg & data->CRITICAL_PWM_ENABLE_MASK) { - err = nct6775_read_value(data, data->REG_CRITICAL_PWM[i], ®); - if (err) - return err; - } else { - reg = 0xff; - } - data->auto_pwm[i][data->auto_pwm_num] = reg; - break; - } - } - - return 0; -} - -static struct nct6775_data *nct6775_update_device(struct device *dev) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - int i, j, err = 0; - u16 reg; - - mutex_lock(&data->update_lock); - - if (time_after(jiffies, data->last_updated + HZ + HZ / 2) - || !data->valid) { - /* Fan clock dividers */ - err = nct6775_update_fan_div_common(data); - if (err) - goto out; - - /* Measured voltages and limits */ - for (i = 0; i < data->in_num; i++) { - if (!(data->have_in & BIT(i))) - continue; - - err = nct6775_read_value(data, data->REG_VIN[i], ®); - if (err) - goto out; - data->in[i][0] = reg; - - err = nct6775_read_value(data, data->REG_IN_MINMAX[0][i], ®); - if (err) - goto out; - data->in[i][1] = reg; - - err = nct6775_read_value(data, data->REG_IN_MINMAX[1][i], ®); - if (err) - goto out; - data->in[i][2] = reg; - } - - /* Measured fan speeds and limits */ - for (i = 0; i < ARRAY_SIZE(data->rpm); i++) { - if (!(data->has_fan & BIT(i))) - continue; - - err = nct6775_read_value(data, data->REG_FAN[i], ®); - if (err) - goto out; - data->rpm[i] = data->fan_from_reg(reg, - data->fan_div[i]); - - if (data->has_fan_min & BIT(i)) { - err = nct6775_read_value(data, data->REG_FAN_MIN[i], ®); - if (err) - goto out; - data->fan_min[i] = reg; - } - - if (data->REG_FAN_PULSES[i]) { - err = nct6775_read_value(data, data->REG_FAN_PULSES[i], ®); - if (err) - goto out; - data->fan_pulses[i] = (reg >> data->FAN_PULSE_SHIFT[i]) & 0x03; - } - - err = nct6775_select_fan_div(dev, data, i, reg); - if (err) - goto out; - } - - err = nct6775_update_pwm(dev); - if (err) - goto out; - - err = nct6775_update_pwm_limits(dev); - if (err) - goto out; - - /* Measured temperatures and limits */ - for (i = 0; i < NUM_TEMP; i++) { - if (!(data->have_temp & BIT(i))) - continue; - for (j = 0; j < ARRAY_SIZE(data->reg_temp); j++) { - if (data->reg_temp[j][i]) { - err = nct6775_read_temp(data, data->reg_temp[j][i], ®); - if (err) - goto out; - data->temp[j][i] = reg; - } - } - if (i >= NUM_TEMP_FIXED || - !(data->have_temp_fixed & BIT(i))) - continue; - err = nct6775_read_value(data, data->REG_TEMP_OFFSET[i], ®); - if (err) - goto out; - data->temp_offset[i] = reg; - } - - for (i = 0; i < NUM_TSI_TEMP; i++) { - if (!(data->have_tsi_temp & BIT(i))) - continue; - err = nct6775_read_value(data, data->REG_TSI_TEMP[i], ®); - if (err) - goto out; - data->tsi_temp[i] = reg; - } - - data->alarms = 0; - for (i = 0; i < NUM_REG_ALARM; i++) { - u16 alarm; - - if (!data->REG_ALARM[i]) - continue; - err = nct6775_read_value(data, data->REG_ALARM[i], &alarm); - if (err) - goto out; - data->alarms |= ((u64)alarm) << (i << 3); - } - - data->beeps = 0; - for (i = 0; i < NUM_REG_BEEP; i++) { - u16 beep; - - if (!data->REG_BEEP[i]) - continue; - err = nct6775_read_value(data, data->REG_BEEP[i], &beep); - if (err) - goto out; - data->beeps |= ((u64)beep) << (i << 3); - } - - data->last_updated = jiffies; - data->valid = true; - } -out: - mutex_unlock(&data->update_lock); - return err ? ERR_PTR(err) : 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 index = sattr->index; - int nr = sattr->nr; - - if (IS_ERR(data)) - return PTR_ERR(data); - - 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 index = sattr->index; - int nr = sattr->nr; - unsigned long val; - int err; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - mutex_lock(&data->update_lock); - data->in[nr][index] = in_to_reg(val, nr); - err = nct6775_write_value(data, data->REG_IN_MINMAX[index - 1][nr], data->in[nr][index]); - mutex_unlock(&data->update_lock); - return err ? : 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; - - if (IS_ERR(data)) - return PTR_ERR(data); - - nr = data->ALARM_BITS[sattr->index]; - return sprintf(buf, "%u\n", - (unsigned int)((data->alarms >> nr) & 0x01)); -} - -static int find_temp_source(struct nct6775_data *data, int index, int count) -{ - int source = data->temp_src[index]; - int nr, err; - - for (nr = 0; nr < count; nr++) { - u16 src; - - err = nct6775_read_value(data, data->REG_TEMP_SOURCE[nr], &src); - if (err) - return err; - if ((src & 0x1f) == source) - return nr; - } - return -ENODEV; -} - -static ssize_t -show_temp_alarm(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - struct nct6775_data *data = nct6775_update_device(dev); - unsigned int alarm = 0; - int nr; - - if (IS_ERR(data)) - return PTR_ERR(data); - - /* - * For temperatures, there is no fixed mapping from registers to alarm - * bits. Alarm bits are determined by the temperature source mapping. - */ - nr = find_temp_source(data, sattr->index, data->num_temp_alarms); - if (nr >= 0) { - int bit = data->ALARM_BITS[nr + TEMP_ALARM_BASE]; - - alarm = (data->alarms >> bit) & 0x01; - } - return sprintf(buf, "%u\n", alarm); -} - -static ssize_t -show_beep(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - struct nct6775_data *data = nct6775_update_device(dev); - int nr; - - if (IS_ERR(data)) - return PTR_ERR(data); - - nr = data->BEEP_BITS[sattr->index]; - - return sprintf(buf, "%u\n", - (unsigned int)((data->beeps >> nr) & 0x01)); -} - -static ssize_t -store_beep(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 nct6775_data *data = dev_get_drvdata(dev); - int nr = data->BEEP_BITS[sattr->index]; - int regindex = nr >> 3; - unsigned long val; - int err; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - if (val > 1) - return -EINVAL; - - mutex_lock(&data->update_lock); - if (val) - data->beeps |= (1ULL << nr); - else - data->beeps &= ~(1ULL << nr); - err = nct6775_write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_temp_beep(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - struct nct6775_data *data = nct6775_update_device(dev); - unsigned int beep = 0; - int nr; - - if (IS_ERR(data)) - return PTR_ERR(data); - - /* - * For temperatures, there is no fixed mapping from registers to beep - * enable bits. Beep enable bits are determined by the temperature - * source mapping. - */ - nr = find_temp_source(data, sattr->index, data->num_temp_beeps); - if (nr >= 0) { - int bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; - - beep = (data->beeps >> bit) & 0x01; - } - return sprintf(buf, "%u\n", beep); -} - -static ssize_t -store_temp_beep(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 nct6775_data *data = dev_get_drvdata(dev); - int nr, bit, regindex; - unsigned long val; - int err; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - if (val > 1) - return -EINVAL; - - nr = find_temp_source(data, sattr->index, data->num_temp_beeps); - if (nr < 0) - return nr; - - bit = data->BEEP_BITS[nr + TEMP_ALARM_BASE]; - regindex = bit >> 3; - - mutex_lock(&data->update_lock); - if (val) - data->beeps |= (1ULL << bit); - else - data->beeps &= ~(1ULL << bit); - err = nct6775_write_value(data, data->REG_BEEP[regindex], - (data->beeps >> (regindex << 3)) & 0xff); - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static umode_t nct6775_in_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - int in = index / 5; /* voltage index */ - - if (!(data->have_in & BIT(in))) - return 0; - - return nct6775_attr_mode(data, attr); -} - -SENSOR_TEMPLATE_2(in_input, "in%d_input", 0444, show_in_reg, NULL, 0, 0); -SENSOR_TEMPLATE(in_alarm, "in%d_alarm", 0444, show_alarm, NULL, 0); -SENSOR_TEMPLATE(in_beep, "in%d_beep", 0644, show_beep, store_beep, 0); -SENSOR_TEMPLATE_2(in_min, "in%d_min", 0644, show_in_reg, store_in_reg, 0, 1); -SENSOR_TEMPLATE_2(in_max, "in%d_max", 0644, show_in_reg, store_in_reg, 0, 2); - -/* - * nct6775_in_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 *nct6775_attributes_in_template[] = { - &sensor_dev_template_in_input, - &sensor_dev_template_in_alarm, - &sensor_dev_template_in_beep, - &sensor_dev_template_in_min, - &sensor_dev_template_in_max, - NULL -}; - -static const struct sensor_template_group nct6775_in_template_group = { - .templates = nct6775_attributes_in_template, - .is_visible = nct6775_in_is_visible, -}; - -static ssize_t -show_fan(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->rpm[nr]); -} - -static ssize_t -show_fan_min(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", - data->fan_from_reg_min(data->fan_min[nr], - data->fan_div[nr])); -} - -static ssize_t -show_fan_div(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%u\n", div_from_reg(data->fan_div[nr])); -} - -static ssize_t -store_fan_min(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - unsigned int reg; - u8 new_div; - int err; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - mutex_lock(&data->update_lock); - if (!data->has_fan_div) { - /* NCT6776F or NCT6779D; we know this is a 13 bit register */ - if (!val) { - val = 0xff1f; - } else { - if (val > 1350000U) - val = 135000U; - val = 1350000U / val; - val = (val & 0x1f) | ((val << 3) & 0xff00); - } - data->fan_min[nr] = val; - goto write_min; /* Leave fan divider alone */ - } - if (!val) { - /* No min limit, alarm disabled */ - data->fan_min[nr] = 255; - new_div = data->fan_div[nr]; /* No change */ - dev_info(dev, "fan%u low limit and alarm disabled\n", nr + 1); - goto write_div; - } - reg = 1350000U / val; - if (reg >= 128 * 255) { - /* - * Speed below this value cannot possibly be represented, - * even with the highest divider (128) - */ - data->fan_min[nr] = 254; - new_div = 7; /* 128 == BIT(7) */ - dev_warn(dev, - "fan%u low limit %lu below minimum %u, set to minimum\n", - nr + 1, val, data->fan_from_reg_min(254, 7)); - } else if (!reg) { - /* - * Speed above this value cannot possibly be represented, - * even with the lowest divider (1) - */ - data->fan_min[nr] = 1; - new_div = 0; /* 1 == BIT(0) */ - dev_warn(dev, - "fan%u low limit %lu above maximum %u, set to maximum\n", - nr + 1, val, data->fan_from_reg_min(1, 0)); - } else { - /* - * Automatically pick the best divider, i.e. the one such - * that the min limit will correspond to a register value - * in the 96..192 range - */ - new_div = 0; - while (reg > 192 && new_div < 7) { - reg >>= 1; - new_div++; - } - data->fan_min[nr] = reg; - } - -write_div: - /* - * Write both the fan clock divider (if it changed) and the new - * fan min (unconditionally) - */ - if (new_div != data->fan_div[nr]) { - dev_dbg(dev, "fan%u clock divider changed from %u to %u\n", - nr + 1, div_from_reg(data->fan_div[nr]), - div_from_reg(new_div)); - data->fan_div[nr] = new_div; - err = nct6775_write_fan_div_common(data, nr); - if (err) - goto write_min; - /* Give the chip time to sample a new speed value */ - data->last_updated = jiffies; - } - -write_min: - err = nct6775_write_value(data, data->REG_FAN_MIN[nr], data->fan_min[nr]); - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static ssize_t -show_fan_pulses(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 p; - - if (IS_ERR(data)) - return PTR_ERR(data); - - p = data->fan_pulses[sattr->index]; - return sprintf(buf, "%d\n", p ? : 4); -} - -static ssize_t -store_fan_pulses(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - u16 reg; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - if (val > 4) - return -EINVAL; - - mutex_lock(&data->update_lock); - data->fan_pulses[nr] = val & 3; - err = nct6775_read_value(data, data->REG_FAN_PULSES[nr], ®); - if (err) - goto out; - reg &= ~(0x03 << data->FAN_PULSE_SHIFT[nr]); - reg |= (val & 3) << data->FAN_PULSE_SHIFT[nr]; - err = nct6775_write_value(data, data->REG_FAN_PULSES[nr], reg); -out: - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static umode_t nct6775_fan_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - int fan = index / 6; /* fan index */ - int nr = index % 6; /* attribute index */ - - if (!(data->has_fan & BIT(fan))) - return 0; - - if (nr == 1 && data->ALARM_BITS[FAN_ALARM_BASE + fan] == -1) - return 0; - if (nr == 2 && data->BEEP_BITS[FAN_ALARM_BASE + fan] == -1) - return 0; - if (nr == 3 && !data->REG_FAN_PULSES[fan]) - return 0; - if (nr == 4 && !(data->has_fan_min & BIT(fan))) - return 0; - if (nr == 5 && data->kind != nct6775) - return 0; - - return nct6775_attr_mode(data, attr); -} - -SENSOR_TEMPLATE(fan_input, "fan%d_input", 0444, show_fan, NULL, 0); -SENSOR_TEMPLATE(fan_alarm, "fan%d_alarm", 0444, show_alarm, NULL, FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_beep, "fan%d_beep", 0644, show_beep, store_beep, FAN_ALARM_BASE); -SENSOR_TEMPLATE(fan_pulses, "fan%d_pulses", 0644, show_fan_pulses, store_fan_pulses, 0); -SENSOR_TEMPLATE(fan_min, "fan%d_min", 0644, show_fan_min, store_fan_min, 0); -SENSOR_TEMPLATE(fan_div, "fan%d_div", 0444, show_fan_div, NULL, 0); - -/* - * nct6775_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 *nct6775_attributes_fan_template[] = { - &sensor_dev_template_fan_input, - &sensor_dev_template_fan_alarm, /* 1 */ - &sensor_dev_template_fan_beep, /* 2 */ - &sensor_dev_template_fan_pulses, - &sensor_dev_template_fan_min, /* 4 */ - &sensor_dev_template_fan_div, /* 5 */ - NULL -}; - -static const struct sensor_template_group nct6775_fan_template_group = { - .templates = nct6775_attributes_fan_template, - .is_visible = nct6775_fan_is_visible, - .base = 1, -}; - -static ssize_t -show_temp_label(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%s\n", data->temp_label[data->temp_src[nr]]); -} - -static ssize_t -show_temp(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; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", LM75_TEMP_FROM_REG(data->temp[index][nr])); -} - -static ssize_t -store_temp(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; - int err; - long val; - - err = kstrtol(buf, 10, &val); - if (err < 0) - return err; - - mutex_lock(&data->update_lock); - data->temp[index][nr] = LM75_TEMP_TO_REG(val); - err = nct6775_write_temp(data, data->reg_temp[index][nr], data->temp[index][nr]); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_temp_offset(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->temp_offset[sattr->index] * 1000); -} - -static ssize_t -store_temp_offset(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - long val; - int err; - - err = kstrtol(buf, 10, &val); - if (err < 0) - return err; - - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), -128, 127); - - mutex_lock(&data->update_lock); - data->temp_offset[nr] = val; - err = nct6775_write_value(data, data->REG_TEMP_OFFSET[nr], val); - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static ssize_t -show_temp_type(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", (int)data->temp_type[nr]); -} - -static ssize_t -store_temp_type(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct nct6775_data *data = nct6775_update_device(dev); - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - u8 vbit, dbit; - u16 vbat, diode; - - if (IS_ERR(data)) - return PTR_ERR(data); - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - if (val != 1 && val != 3 && val != 4) - return -EINVAL; - - mutex_lock(&data->update_lock); - - data->temp_type[nr] = val; - vbit = 0x02 << nr; - dbit = data->DIODE_MASK << nr; - - err = nct6775_read_value(data, data->REG_VBAT, &vbat); - if (err) - goto out; - vbat &= ~vbit; - - err = nct6775_read_value(data, data->REG_DIODE, &diode); - if (err) - goto out; - diode &= ~dbit; - - switch (val) { - case 1: /* CPU diode (diode, current mode) */ - vbat |= vbit; - diode |= dbit; - break; - case 3: /* diode, voltage mode */ - vbat |= dbit; - break; - case 4: /* thermistor */ - break; - } - err = nct6775_write_value(data, data->REG_VBAT, vbat); - if (err) - goto out; - err = nct6775_write_value(data, data->REG_DIODE, diode); -out: - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static umode_t nct6775_temp_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - int temp = index / 10; /* temp index */ - int nr = index % 10; /* attribute index */ - - if (!(data->have_temp & BIT(temp))) - return 0; - - if (nr == 1 && !data->temp_label) - return 0; - - if (nr == 2 && find_temp_source(data, temp, data->num_temp_alarms) < 0) - return 0; /* alarm */ - - if (nr == 3 && find_temp_source(data, temp, data->num_temp_beeps) < 0) - return 0; /* beep */ - - if (nr == 4 && !data->reg_temp[1][temp]) /* max */ - return 0; - - if (nr == 5 && !data->reg_temp[2][temp]) /* max_hyst */ - return 0; - - if (nr == 6 && !data->reg_temp[3][temp]) /* crit */ - return 0; - - if (nr == 7 && !data->reg_temp[4][temp]) /* lcrit */ - return 0; - - /* offset and type only apply to fixed sensors */ - if (nr > 7 && !(data->have_temp_fixed & BIT(temp))) - return 0; - - return nct6775_attr_mode(data, attr); -} - -SENSOR_TEMPLATE_2(temp_input, "temp%d_input", 0444, show_temp, NULL, 0, 0); -SENSOR_TEMPLATE(temp_label, "temp%d_label", 0444, show_temp_label, NULL, 0); -SENSOR_TEMPLATE_2(temp_max, "temp%d_max", 0644, show_temp, store_temp, 0, 1); -SENSOR_TEMPLATE_2(temp_max_hyst, "temp%d_max_hyst", 0644, show_temp, store_temp, 0, 2); -SENSOR_TEMPLATE_2(temp_crit, "temp%d_crit", 0644, show_temp, store_temp, 0, 3); -SENSOR_TEMPLATE_2(temp_lcrit, "temp%d_lcrit", 0644, show_temp, store_temp, 0, 4); -SENSOR_TEMPLATE(temp_offset, "temp%d_offset", 0644, show_temp_offset, store_temp_offset, 0); -SENSOR_TEMPLATE(temp_type, "temp%d_type", 0644, show_temp_type, store_temp_type, 0); -SENSOR_TEMPLATE(temp_alarm, "temp%d_alarm", 0444, show_temp_alarm, NULL, 0); -SENSOR_TEMPLATE(temp_beep, "temp%d_beep", 0644, show_temp_beep, store_temp_beep, 0); - -/* - * nct6775_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 *nct6775_attributes_temp_template[] = { - &sensor_dev_template_temp_input, - &sensor_dev_template_temp_label, - &sensor_dev_template_temp_alarm, /* 2 */ - &sensor_dev_template_temp_beep, /* 3 */ - &sensor_dev_template_temp_max, /* 4 */ - &sensor_dev_template_temp_max_hyst, /* 5 */ - &sensor_dev_template_temp_crit, /* 6 */ - &sensor_dev_template_temp_lcrit, /* 7 */ - &sensor_dev_template_temp_offset, /* 8 */ - &sensor_dev_template_temp_type, /* 9 */ - NULL -}; - -static const struct sensor_template_group nct6775_temp_template_group = { - .templates = nct6775_attributes_temp_template, - .is_visible = nct6775_temp_is_visible, - .base = 1, -}; - -static ssize_t show_tsi_temp(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sysfs_emit(buf, "%u\n", tsi_temp_from_reg(data->tsi_temp[sattr->index])); -} - -static ssize_t show_tsi_temp_label(struct device *dev, struct device_attribute *attr, char *buf) -{ - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - - return sysfs_emit(buf, "TSI%d_TEMP\n", sattr->index); -} - -SENSOR_TEMPLATE(tsi_temp_input, "temp%d_input", 0444, show_tsi_temp, NULL, 0); -SENSOR_TEMPLATE(tsi_temp_label, "temp%d_label", 0444, show_tsi_temp_label, NULL, 0); - -static umode_t nct6775_tsi_temp_is_visible(struct kobject *kobj, struct attribute *attr, - int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - int temp = index / 2; - - return (data->have_tsi_temp & BIT(temp)) ? nct6775_attr_mode(data, attr) : 0; -} - -/* - * The index calculation in nct6775_tsi_temp_is_visible() must be kept in - * sync with the size of this array. - */ -static struct sensor_device_template *nct6775_tsi_temp_template[] = { - &sensor_dev_template_tsi_temp_input, - &sensor_dev_template_tsi_temp_label, - NULL -}; - -static ssize_t -show_pwm_mode(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->pwm_mode[sattr->index]); -} - -static ssize_t -store_pwm_mode(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - u16 reg; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - if (val > 1) - return -EINVAL; - - /* Setting DC mode (0) is not supported for all chips/channels */ - if (data->REG_PWM_MODE[nr] == 0) { - if (!val) - return -EINVAL; - return count; - } - - mutex_lock(&data->update_lock); - data->pwm_mode[nr] = val; - err = nct6775_read_value(data, data->REG_PWM_MODE[nr], ®); - if (err) - goto out; - reg &= ~data->PWM_MODE_MASK[nr]; - if (!val) - reg |= data->PWM_MODE_MASK[nr]; - err = nct6775_write_value(data, data->REG_PWM_MODE[nr], reg); -out: - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_pwm(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; - int err; - u16 pwm; - - if (IS_ERR(data)) - return PTR_ERR(data); - - /* - * For automatic fan control modes, show current pwm readings. - * Otherwise, show the configured value. - */ - if (index == 0 && data->pwm_enable[nr] > manual) { - err = nct6775_read_value(data, data->REG_PWM_READ[nr], &pwm); - if (err) - return err; - } else { - pwm = data->pwm[index][nr]; - } - - return sprintf(buf, "%d\n", pwm); -} - -static ssize_t -store_pwm(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 minval[7] = { 0, 1, 1, data->pwm[2][nr], 0, 0, 0 }; - int maxval[7] - = { 255, 255, data->pwm[3][nr] ? : 255, 255, 255, 255, 255 }; - int err; - u16 reg; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - val = clamp_val(val, minval[index], maxval[index]); - - mutex_lock(&data->update_lock); - data->pwm[index][nr] = val; - err = nct6775_write_value(data, data->REG_PWM[index][nr], val); - if (err) - goto out; - if (index == 2) { /* floor: disable if val == 0 */ - err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); - if (err) - goto out; - reg &= 0x7f; - if (val) - reg |= 0x80; - err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); - } -out: - mutex_unlock(&data->update_lock); - return err ? : count; -} - -/* Returns 0 if OK, -EINVAL otherwise */ -static int check_trip_points(struct nct6775_data *data, int nr) -{ - int i; - - for (i = 0; i < data->auto_pwm_num - 1; i++) { - if (data->auto_temp[nr][i] > data->auto_temp[nr][i + 1]) - return -EINVAL; - } - for (i = 0; i < data->auto_pwm_num - 1; i++) { - if (data->auto_pwm[nr][i] > data->auto_pwm[nr][i + 1]) - return -EINVAL; - } - /* validate critical temperature and pwm if enabled (pwm > 0) */ - if (data->auto_pwm[nr][data->auto_pwm_num]) { - if (data->auto_temp[nr][data->auto_pwm_num - 1] > - data->auto_temp[nr][data->auto_pwm_num] || - data->auto_pwm[nr][data->auto_pwm_num - 1] > - data->auto_pwm[nr][data->auto_pwm_num]) - return -EINVAL; - } - return 0; -} - -static int pwm_update_registers(struct nct6775_data *data, int nr) -{ - u16 reg; - int err; - - switch (data->pwm_enable[nr]) { - case off: - case manual: - break; - case speed_cruise: - err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); - if (err) - return err; - reg = (reg & ~data->tolerance_mask) | - (data->target_speed_tolerance[nr] & data->tolerance_mask); - err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); - if (err) - return err; - err = nct6775_write_value(data, data->REG_TARGET[nr], - data->target_speed[nr] & 0xff); - if (err) - return err; - if (data->REG_TOLERANCE_H) { - reg = (data->target_speed[nr] >> 8) & 0x0f; - reg |= (data->target_speed_tolerance[nr] & 0x38) << 1; - err = nct6775_write_value(data, data->REG_TOLERANCE_H[nr], reg); - if (err) - return err; - } - break; - case thermal_cruise: - err = nct6775_write_value(data, data->REG_TARGET[nr], data->target_temp[nr]); - if (err) - return err; - fallthrough; - default: - err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); - if (err) - return err; - reg = (reg & ~data->tolerance_mask) | - data->temp_tolerance[0][nr]; - err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); - if (err) - return err; - break; - } - - return 0; -} - -static ssize_t -show_pwm_enable(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->pwm_enable[sattr->index]); -} - -static ssize_t -store_pwm_enable(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - u16 reg; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - if (val > sf4) - return -EINVAL; - - if (val == sf3 && data->kind != nct6775) - return -EINVAL; - - if (val == sf4 && check_trip_points(data, nr)) { - dev_err(dev, "Inconsistent trip points, not switching to SmartFan IV mode\n"); - dev_err(dev, "Adjust trip points and try again\n"); - return -EINVAL; - } - - mutex_lock(&data->update_lock); - data->pwm_enable[nr] = val; - if (val == off) { - /* - * turn off pwm control: select manual mode, set pwm to maximum - */ - data->pwm[0][nr] = 255; - err = nct6775_write_value(data, data->REG_PWM[0][nr], 255); - if (err) - goto out; - } - err = pwm_update_registers(data, nr); - if (err) - goto out; - err = nct6775_read_value(data, data->REG_FAN_MODE[nr], ®); - if (err) - goto out; - reg &= 0x0f; - reg |= pwm_enable_to_reg(val) << 4; - err = nct6775_write_value(data, data->REG_FAN_MODE[nr], reg); -out: - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_pwm_temp_sel_common(struct nct6775_data *data, char *buf, int src) -{ - int i, sel = 0; - - for (i = 0; i < NUM_TEMP; i++) { - if (!(data->have_temp & BIT(i))) - continue; - if (src == data->temp_src[i]) { - sel = i + 1; - break; - } - } - - return sprintf(buf, "%d\n", sel); -} - -static ssize_t -show_pwm_temp_sel(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 index = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return show_pwm_temp_sel_common(data, buf, data->pwm_temp_sel[index]); -} - -static ssize_t -store_pwm_temp_sel(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct nct6775_data *data = nct6775_update_device(dev); - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err, src; - u16 reg; - - if (IS_ERR(data)) - return PTR_ERR(data); - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - if (val == 0 || val > NUM_TEMP) - return -EINVAL; - if (!(data->have_temp & BIT(val - 1)) || !data->temp_src[val - 1]) - return -EINVAL; - - mutex_lock(&data->update_lock); - src = data->temp_src[val - 1]; - data->pwm_temp_sel[nr] = src; - err = nct6775_read_value(data, data->REG_TEMP_SEL[nr], ®); - if (err) - goto out; - reg &= 0xe0; - reg |= src; - err = nct6775_write_value(data, data->REG_TEMP_SEL[nr], reg); -out: - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static ssize_t -show_pwm_weight_temp_sel(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 index = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return show_pwm_temp_sel_common(data, buf, - data->pwm_weight_temp_sel[index]); -} - -static ssize_t -store_pwm_weight_temp_sel(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct nct6775_data *data = nct6775_update_device(dev); - struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err, src; - u16 reg; - - if (IS_ERR(data)) - return PTR_ERR(data); - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - if (val > NUM_TEMP) - return -EINVAL; - val = array_index_nospec(val, NUM_TEMP + 1); - if (val && (!(data->have_temp & BIT(val - 1)) || - !data->temp_src[val - 1])) - return -EINVAL; - - mutex_lock(&data->update_lock); - if (val) { - src = data->temp_src[val - 1]; - data->pwm_weight_temp_sel[nr] = src; - err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); - if (err) - goto out; - reg &= 0xe0; - reg |= (src | 0x80); - err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); - } else { - data->pwm_weight_temp_sel[nr] = 0; - err = nct6775_read_value(data, data->REG_WEIGHT_TEMP_SEL[nr], ®); - if (err) - goto out; - reg &= 0x7f; - err = nct6775_write_value(data, data->REG_WEIGHT_TEMP_SEL[nr], reg); - } -out: - mutex_unlock(&data->update_lock); - - return err ? : count; -} - -static ssize_t -show_target_temp(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->target_temp[sattr->index] * 1000); -} - -static ssize_t -store_target_temp(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, - data->target_temp_mask); - - mutex_lock(&data->update_lock); - data->target_temp[nr] = val; - err = pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_target_speed(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 = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", - fan_from_reg16(data->target_speed[nr], - data->fan_div[nr])); -} - -static ssize_t -store_target_speed(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - u16 speed; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - val = clamp_val(val, 0, 1350000U); - speed = fan_to_reg(val, data->fan_div[nr]); - - mutex_lock(&data->update_lock); - data->target_speed[nr] = speed; - err = pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_temp_tolerance(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; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->temp_tolerance[index][nr] * 1000); -} - -static ssize_t -store_temp_tolerance(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; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - /* Limit tolerance as needed */ - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, data->tolerance_mask); - - mutex_lock(&data->update_lock); - data->temp_tolerance[index][nr] = val; - if (index) - err = pwm_update_registers(data, nr); - else - err = nct6775_write_value(data, data->REG_CRITICAL_TEMP_TOLERANCE[nr], val); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -/* - * Fan speed tolerance is a tricky beast, since the associated register is - * a tick counter, but the value is reported and configured as rpm. - * Compute resulting low and high rpm values and report the difference. - * A fan speed tolerance only makes sense if a fan target speed has been - * configured, so only display values other than 0 if that is the case. - */ -static ssize_t -show_speed_tolerance(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 = sattr->index; - int target, tolerance = 0; - - if (IS_ERR(data)) - return PTR_ERR(data); - - target = data->target_speed[nr]; - - if (target) { - int low = target - data->target_speed_tolerance[nr]; - int high = target + data->target_speed_tolerance[nr]; - - if (low <= 0) - low = 1; - if (high > 0xffff) - high = 0xffff; - if (high < low) - high = low; - - tolerance = (fan_from_reg16(low, data->fan_div[nr]) - - fan_from_reg16(high, data->fan_div[nr])) / 2; - } - - return sprintf(buf, "%d\n", tolerance); -} - -static ssize_t -store_speed_tolerance(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 *sattr = to_sensor_dev_attr(attr); - int nr = sattr->index; - unsigned long val; - int err; - int low, high; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - high = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) + val; - low = fan_from_reg16(data->target_speed[nr], data->fan_div[nr]) - val; - if (low <= 0) - low = 1; - if (high < low) - high = low; - - val = (fan_to_reg(low, data->fan_div[nr]) - - fan_to_reg(high, data->fan_div[nr])) / 2; - - /* Limit tolerance as needed */ - val = clamp_val(val, 0, data->speed_tolerance_limit); - - mutex_lock(&data->update_lock); - data->target_speed_tolerance[nr] = val; - err = pwm_update_registers(data, nr); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -SENSOR_TEMPLATE_2(pwm, "pwm%d", 0644, show_pwm, store_pwm, 0, 0); -SENSOR_TEMPLATE(pwm_mode, "pwm%d_mode", 0644, show_pwm_mode, store_pwm_mode, 0); -SENSOR_TEMPLATE(pwm_enable, "pwm%d_enable", 0644, show_pwm_enable, store_pwm_enable, 0); -SENSOR_TEMPLATE(pwm_temp_sel, "pwm%d_temp_sel", 0644, show_pwm_temp_sel, store_pwm_temp_sel, 0); -SENSOR_TEMPLATE(pwm_target_temp, "pwm%d_target_temp", 0644, show_target_temp, store_target_temp, 0); -SENSOR_TEMPLATE(fan_target, "fan%d_target", 0644, show_target_speed, store_target_speed, 0); -SENSOR_TEMPLATE(fan_tolerance, "fan%d_tolerance", 0644, show_speed_tolerance, - store_speed_tolerance, 0); - -/* Smart Fan registers */ - -static ssize_t -show_weight_temp(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; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->weight_temp[index][nr] * 1000); -} - -static ssize_t -store_weight_temp(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; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - val = clamp_val(DIV_ROUND_CLOSEST(val, 1000), 0, 255); - - mutex_lock(&data->update_lock); - data->weight_temp[index][nr] = val; - err = nct6775_write_value(data, data->REG_WEIGHT_TEMP[index][nr], val); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -SENSOR_TEMPLATE(pwm_weight_temp_sel, "pwm%d_weight_temp_sel", 0644, - show_pwm_weight_temp_sel, store_pwm_weight_temp_sel, 0); -SENSOR_TEMPLATE_2(pwm_weight_temp_step, "pwm%d_weight_temp_step", - 0644, show_weight_temp, store_weight_temp, 0, 0); -SENSOR_TEMPLATE_2(pwm_weight_temp_step_tol, "pwm%d_weight_temp_step_tol", - 0644, show_weight_temp, store_weight_temp, 0, 1); -SENSOR_TEMPLATE_2(pwm_weight_temp_step_base, "pwm%d_weight_temp_step_base", - 0644, show_weight_temp, store_weight_temp, 0, 2); -SENSOR_TEMPLATE_2(pwm_weight_duty_step, "pwm%d_weight_duty_step", 0644, show_pwm, store_pwm, 0, 5); -SENSOR_TEMPLATE_2(pwm_weight_duty_base, "pwm%d_weight_duty_base", 0644, show_pwm, store_pwm, 0, 6); - -static ssize_t -show_fan_time(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; - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", - step_time_from_reg(data->fan_time[index][nr], - data->pwm_mode[nr])); -} - -static ssize_t -store_fan_time(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; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - - val = step_time_to_reg(val, data->pwm_mode[nr]); - mutex_lock(&data->update_lock); - data->fan_time[index][nr] = val; - err = nct6775_write_value(data, data->REG_FAN_TIME[index][nr], val); - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_auto_pwm(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); - - if (IS_ERR(data)) - return PTR_ERR(data); - - return sprintf(buf, "%d\n", data->auto_pwm[sattr->nr][sattr->index]); -} - -static ssize_t -store_auto_pwm(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 point = sattr->index; - unsigned long val; - int err; - u16 reg; - - err = kstrtoul(buf, 10, &val); - if (err < 0) - return err; - if (val > 255) - return -EINVAL; - - if (point == data->auto_pwm_num) { - if (data->kind != nct6775 && !val) - return -EINVAL; - if (data->kind != nct6779 && val) - val = 0xff; - } - - mutex_lock(&data->update_lock); - data->auto_pwm[nr][point] = val; - if (point < data->auto_pwm_num) { - err = nct6775_write_value(data, NCT6775_AUTO_PWM(data, nr, point), - data->auto_pwm[nr][point]); - } else { - switch (data->kind) { - case nct6775: - /* disable if needed (pwm == 0) */ - err = nct6775_read_value(data, NCT6775_REG_CRITICAL_ENAB[nr], ®); - if (err) - break; - if (val) - reg |= 0x02; - else - reg &= ~0x02; - err = nct6775_write_value(data, NCT6775_REG_CRITICAL_ENAB[nr], reg); - break; - case nct6776: - break; /* always enabled, nothing to do */ - case nct6106: - case nct6116: - case nct6779: - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - err = nct6775_write_value(data, data->REG_CRITICAL_PWM[nr], val); - if (err) - break; - err = nct6775_read_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], ®); - if (err) - break; - if (val == 255) - reg &= ~data->CRITICAL_PWM_ENABLE_MASK; - else - reg |= data->CRITICAL_PWM_ENABLE_MASK; - err = nct6775_write_value(data, data->REG_CRITICAL_PWM_ENABLE[nr], reg); - break; - } - } - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static ssize_t -show_auto_temp(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 point = sattr->index; - - if (IS_ERR(data)) - return PTR_ERR(data); - - /* - * We don't know for sure if the temperature is signed or unsigned. - * Assume it is unsigned. - */ - return sprintf(buf, "%d\n", data->auto_temp[nr][point] * 1000); -} - -static ssize_t -store_auto_temp(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 point = sattr->index; - unsigned long val; - int err; - - err = kstrtoul(buf, 10, &val); - if (err) - return err; - if (val > 255000) - return -EINVAL; - - mutex_lock(&data->update_lock); - data->auto_temp[nr][point] = DIV_ROUND_CLOSEST(val, 1000); - if (point < data->auto_pwm_num) { - err = nct6775_write_value(data, NCT6775_AUTO_TEMP(data, nr, point), - data->auto_temp[nr][point]); - } else { - err = nct6775_write_value(data, data->REG_CRITICAL_TEMP[nr], - data->auto_temp[nr][point]); - } - mutex_unlock(&data->update_lock); - return err ? : count; -} - -static umode_t nct6775_pwm_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - int pwm = index / 36; /* pwm index */ - int nr = index % 36; /* attribute index */ - - if (!(data->has_pwm & BIT(pwm))) - return 0; - - if ((nr >= 14 && nr <= 18) || nr == 21) /* weight */ - if (!data->REG_WEIGHT_TEMP_SEL[pwm]) - return 0; - if (nr == 19 && data->REG_PWM[3] == NULL) /* pwm_max */ - return 0; - if (nr == 20 && data->REG_PWM[4] == NULL) /* pwm_step */ - return 0; - if (nr == 21 && data->REG_PWM[6] == NULL) /* weight_duty_base */ - return 0; - - if (nr >= 22 && nr <= 35) { /* auto point */ - int api = (nr - 22) / 2; /* auto point index */ - - if (api > data->auto_pwm_num) - return 0; - } - return nct6775_attr_mode(data, attr); -} - -SENSOR_TEMPLATE_2(pwm_stop_time, "pwm%d_stop_time", 0644, show_fan_time, store_fan_time, 0, 0); -SENSOR_TEMPLATE_2(pwm_step_up_time, "pwm%d_step_up_time", 0644, - show_fan_time, store_fan_time, 0, 1); -SENSOR_TEMPLATE_2(pwm_step_down_time, "pwm%d_step_down_time", 0644, - show_fan_time, store_fan_time, 0, 2); -SENSOR_TEMPLATE_2(pwm_start, "pwm%d_start", 0644, show_pwm, store_pwm, 0, 1); -SENSOR_TEMPLATE_2(pwm_floor, "pwm%d_floor", 0644, show_pwm, store_pwm, 0, 2); -SENSOR_TEMPLATE_2(pwm_temp_tolerance, "pwm%d_temp_tolerance", 0644, - show_temp_tolerance, store_temp_tolerance, 0, 0); -SENSOR_TEMPLATE_2(pwm_crit_temp_tolerance, "pwm%d_crit_temp_tolerance", - 0644, show_temp_tolerance, store_temp_tolerance, 0, 1); - -SENSOR_TEMPLATE_2(pwm_max, "pwm%d_max", 0644, show_pwm, store_pwm, 0, 3); - -SENSOR_TEMPLATE_2(pwm_step, "pwm%d_step", 0644, show_pwm, store_pwm, 0, 4); - -SENSOR_TEMPLATE_2(pwm_auto_point1_pwm, "pwm%d_auto_point1_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 0); -SENSOR_TEMPLATE_2(pwm_auto_point1_temp, "pwm%d_auto_point1_temp", - 0644, show_auto_temp, store_auto_temp, 0, 0); - -SENSOR_TEMPLATE_2(pwm_auto_point2_pwm, "pwm%d_auto_point2_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 1); -SENSOR_TEMPLATE_2(pwm_auto_point2_temp, "pwm%d_auto_point2_temp", - 0644, show_auto_temp, store_auto_temp, 0, 1); - -SENSOR_TEMPLATE_2(pwm_auto_point3_pwm, "pwm%d_auto_point3_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 2); -SENSOR_TEMPLATE_2(pwm_auto_point3_temp, "pwm%d_auto_point3_temp", - 0644, show_auto_temp, store_auto_temp, 0, 2); - -SENSOR_TEMPLATE_2(pwm_auto_point4_pwm, "pwm%d_auto_point4_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 3); -SENSOR_TEMPLATE_2(pwm_auto_point4_temp, "pwm%d_auto_point4_temp", - 0644, show_auto_temp, store_auto_temp, 0, 3); - -SENSOR_TEMPLATE_2(pwm_auto_point5_pwm, "pwm%d_auto_point5_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 4); -SENSOR_TEMPLATE_2(pwm_auto_point5_temp, "pwm%d_auto_point5_temp", - 0644, show_auto_temp, store_auto_temp, 0, 4); - -SENSOR_TEMPLATE_2(pwm_auto_point6_pwm, "pwm%d_auto_point6_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 5); -SENSOR_TEMPLATE_2(pwm_auto_point6_temp, "pwm%d_auto_point6_temp", - 0644, show_auto_temp, store_auto_temp, 0, 5); - -SENSOR_TEMPLATE_2(pwm_auto_point7_pwm, "pwm%d_auto_point7_pwm", - 0644, show_auto_pwm, store_auto_pwm, 0, 6); -SENSOR_TEMPLATE_2(pwm_auto_point7_temp, "pwm%d_auto_point7_temp", - 0644, show_auto_temp, store_auto_temp, 0, 6); - -/* - * nct6775_pwm_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 *nct6775_attributes_pwm_template[] = { - &sensor_dev_template_pwm, - &sensor_dev_template_pwm_mode, - &sensor_dev_template_pwm_enable, - &sensor_dev_template_pwm_temp_sel, - &sensor_dev_template_pwm_temp_tolerance, - &sensor_dev_template_pwm_crit_temp_tolerance, - &sensor_dev_template_pwm_target_temp, - &sensor_dev_template_fan_target, - &sensor_dev_template_fan_tolerance, - &sensor_dev_template_pwm_stop_time, - &sensor_dev_template_pwm_step_up_time, - &sensor_dev_template_pwm_step_down_time, - &sensor_dev_template_pwm_start, - &sensor_dev_template_pwm_floor, - &sensor_dev_template_pwm_weight_temp_sel, /* 14 */ - &sensor_dev_template_pwm_weight_temp_step, - &sensor_dev_template_pwm_weight_temp_step_tol, - &sensor_dev_template_pwm_weight_temp_step_base, - &sensor_dev_template_pwm_weight_duty_step, /* 18 */ - &sensor_dev_template_pwm_max, /* 19 */ - &sensor_dev_template_pwm_step, /* 20 */ - &sensor_dev_template_pwm_weight_duty_base, /* 21 */ - &sensor_dev_template_pwm_auto_point1_pwm, /* 22 */ - &sensor_dev_template_pwm_auto_point1_temp, - &sensor_dev_template_pwm_auto_point2_pwm, - &sensor_dev_template_pwm_auto_point2_temp, - &sensor_dev_template_pwm_auto_point3_pwm, - &sensor_dev_template_pwm_auto_point3_temp, - &sensor_dev_template_pwm_auto_point4_pwm, - &sensor_dev_template_pwm_auto_point4_temp, - &sensor_dev_template_pwm_auto_point5_pwm, - &sensor_dev_template_pwm_auto_point5_temp, - &sensor_dev_template_pwm_auto_point6_pwm, - &sensor_dev_template_pwm_auto_point6_temp, - &sensor_dev_template_pwm_auto_point7_pwm, - &sensor_dev_template_pwm_auto_point7_temp, /* 35 */ - - NULL -}; - -static const struct sensor_template_group nct6775_pwm_template_group = { - .templates = nct6775_attributes_pwm_template, - .is_visible = nct6775_pwm_is_visible, - .base = 1, -}; - -static ssize_t -cpu0_vid_show(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_RO(cpu0_vid); - -/* Case open detection */ - -static ssize_t -clear_caseopen(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - struct nct6775_sio_data *sio_data = data->sio_data; - int nr = to_sensor_dev_attr(attr)->index - INTRUSION_ALARM_BASE; - 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. - * The CR registers are the same for all chips, and not all chips - * support clearing the caseopen status through "regular" registers. - */ - ret = sio_data->sio_enter(sio_data); - if (ret) { - count = ret; - goto error; - } - - sio_data->sio_select(sio_data, NCT6775_LD_ACPI); - reg = sio_data->sio_inb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr]); - reg |= NCT6775_CR_CASEOPEN_CLR_MASK[nr]; - sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); - reg &= ~NCT6775_CR_CASEOPEN_CLR_MASK[nr]; - sio_data->sio_outb(sio_data, NCT6775_REG_CR_CASEOPEN_CLR[nr], reg); - sio_data->sio_exit(sio_data); - - data->valid = false; /* Force cache refresh */ -error: - mutex_unlock(&data->update_lock); - return count; -} - -static SENSOR_DEVICE_ATTR(intrusion0_alarm, 0644, show_alarm, clear_caseopen, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_alarm, 0644, show_alarm, - clear_caseopen, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(intrusion0_beep, 0644, show_beep, store_beep, INTRUSION_ALARM_BASE); -static SENSOR_DEVICE_ATTR(intrusion1_beep, 0644, show_beep, store_beep, INTRUSION_ALARM_BASE + 1); -static SENSOR_DEVICE_ATTR(beep_enable, 0644, show_beep, store_beep, BEEP_ENABLE_BASE); - -static umode_t nct6775_other_is_visible(struct kobject *kobj, - struct attribute *attr, int index) -{ - struct device *dev = kobj_to_dev(kobj); - struct nct6775_data *data = dev_get_drvdata(dev); - - if (index == 0 && !data->have_vid) - return 0; - - if (index == 1 || index == 2) { - if (data->ALARM_BITS[INTRUSION_ALARM_BASE + index - 1] < 0) - return 0; - } - - if (index == 3 || index == 4) { - if (data->BEEP_BITS[INTRUSION_ALARM_BASE + index - 3] < 0) - return 0; - } - - return nct6775_attr_mode(data, attr); -} - -/* - * nct6775_other_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 attribute *nct6775_attributes_other[] = { - &dev_attr_cpu0_vid.attr, /* 0 */ - &sensor_dev_attr_intrusion0_alarm.dev_attr.attr, /* 1 */ - &sensor_dev_attr_intrusion1_alarm.dev_attr.attr, /* 2 */ - &sensor_dev_attr_intrusion0_beep.dev_attr.attr, /* 3 */ - &sensor_dev_attr_intrusion1_beep.dev_attr.attr, /* 4 */ - &sensor_dev_attr_beep_enable.dev_attr.attr, /* 5 */ - - NULL -}; - -static const struct attribute_group nct6775_group_other = { - .attrs = nct6775_attributes_other, - .is_visible = nct6775_other_is_visible, -}; - -static inline int nct6775_init_device(struct nct6775_data *data) -{ - int i, err; - u16 tmp, diode; - - /* Start monitoring if needed */ - if (data->REG_CONFIG) { - err = nct6775_read_value(data, data->REG_CONFIG, &tmp); - if (err) - return err; - if (!(tmp & 0x01)) { - err = nct6775_write_value(data, data->REG_CONFIG, tmp | 0x01); - if (err) - return err; - } - } - - /* Enable temperature sensors if needed */ - for (i = 0; i < NUM_TEMP; i++) { - if (!(data->have_temp & BIT(i))) - continue; - if (!data->reg_temp_config[i]) - continue; - err = nct6775_read_value(data, data->reg_temp_config[i], &tmp); - if (err) - return err; - if (tmp & 0x01) { - err = nct6775_write_value(data, data->reg_temp_config[i], tmp & 0xfe); - if (err) - return err; - } - } - - /* Enable VBAT monitoring if needed */ - err = nct6775_read_value(data, data->REG_VBAT, &tmp); - if (err) - return err; - if (!(tmp & 0x01)) { - err = nct6775_write_value(data, data->REG_VBAT, tmp | 0x01); - if (err) - return err; - } - - err = nct6775_read_value(data, data->REG_DIODE, &diode); - if (err) - return err; - - for (i = 0; i < data->temp_fixed_num; i++) { - if (!(data->have_temp_fixed & BIT(i))) - continue; - if ((tmp & (data->DIODE_MASK << i))) /* diode */ - data->temp_type[i] - = 3 - ((diode >> i) & data->DIODE_MASK); - else /* thermistor */ - data->temp_type[i] = 4; - } - - return 0; -} - -static void -nct6775_check_fan_inputs(struct nct6775_data *data, struct nct6775_sio_data *sio_data) -{ - bool fan3pin = false, fan4pin = false, fan4min = false; - bool fan5pin = false, fan6pin = false, fan7pin = false; - bool pwm3pin = false, pwm4pin = false, pwm5pin = false; - bool pwm6pin = false, pwm7pin = false; - - /* Store SIO_REG_ENABLE for use during resume */ - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - data->sio_reg_enable = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - - /* fan4 and fan5 share some pins with the GPIO and serial flash */ - if (data->kind == nct6775) { - int cr2c = sio_data->sio_inb(sio_data, 0x2c); - - fan3pin = cr2c & BIT(6); - pwm3pin = cr2c & BIT(7); - - /* On NCT6775, fan4 shares pins with the fdc interface */ - fan4pin = !(sio_data->sio_inb(sio_data, 0x2A) & 0x80); - } else if (data->kind == nct6776) { - bool gpok = sio_data->sio_inb(sio_data, 0x27) & 0x80; - const char *board_vendor, *board_name; - - board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - board_name = dmi_get_system_info(DMI_BOARD_NAME); - - if (board_name && board_vendor && - !strcmp(board_vendor, "ASRock")) { - /* - * Auxiliary fan monitoring is not enabled on ASRock - * Z77 Pro4-M if booted in UEFI Ultra-FastBoot mode. - * Observed with BIOS version 2.00. - */ - if (!strcmp(board_name, "Z77 Pro4-M")) { - if ((data->sio_reg_enable & 0xe0) != 0xe0) { - data->sio_reg_enable |= 0xe0; - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, - data->sio_reg_enable); - } - } - } - - if (data->sio_reg_enable & 0x80) - fan3pin = gpok; - else - fan3pin = !(sio_data->sio_inb(sio_data, 0x24) & 0x40); - - if (data->sio_reg_enable & 0x40) - fan4pin = gpok; - else - fan4pin = sio_data->sio_inb(sio_data, 0x1C) & 0x01; - - if (data->sio_reg_enable & 0x20) - fan5pin = gpok; - else - fan5pin = sio_data->sio_inb(sio_data, 0x1C) & 0x02; - - fan4min = fan4pin; - pwm3pin = fan3pin; - } else if (data->kind == nct6106) { - int cr24 = sio_data->sio_inb(sio_data, 0x24); - - fan3pin = !(cr24 & 0x80); - pwm3pin = cr24 & 0x08; - } else if (data->kind == nct6116) { - int cr1a = sio_data->sio_inb(sio_data, 0x1a); - int cr1b = sio_data->sio_inb(sio_data, 0x1b); - int cr24 = sio_data->sio_inb(sio_data, 0x24); - int cr2a = sio_data->sio_inb(sio_data, 0x2a); - int cr2b = sio_data->sio_inb(sio_data, 0x2b); - int cr2f = sio_data->sio_inb(sio_data, 0x2f); - - fan3pin = !(cr2b & 0x10); - fan4pin = (cr2b & 0x80) || // pin 1(2) - (!(cr2f & 0x10) && (cr1a & 0x04)); // pin 65(66) - fan5pin = (cr2b & 0x80) || // pin 126(127) - (!(cr1b & 0x03) && (cr2a & 0x02)); // pin 94(96) - - pwm3pin = fan3pin && (cr24 & 0x08); - pwm4pin = fan4pin; - pwm5pin = fan5pin; - } else { - /* - * NCT6779D, NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, - * NCT6797D, NCT6798D - */ - int cr1a = sio_data->sio_inb(sio_data, 0x1a); - int cr1b = sio_data->sio_inb(sio_data, 0x1b); - int cr1c = sio_data->sio_inb(sio_data, 0x1c); - int cr1d = sio_data->sio_inb(sio_data, 0x1d); - int cr2a = sio_data->sio_inb(sio_data, 0x2a); - int cr2b = sio_data->sio_inb(sio_data, 0x2b); - int cr2d = sio_data->sio_inb(sio_data, 0x2d); - int cr2f = sio_data->sio_inb(sio_data, 0x2f); - bool dsw_en = cr2f & BIT(3); - bool ddr4_en = cr2f & BIT(4); - int cre0; - int creb; - int cred; - - sio_data->sio_select(sio_data, NCT6775_LD_12); - cre0 = sio_data->sio_inb(sio_data, 0xe0); - creb = sio_data->sio_inb(sio_data, 0xeb); - cred = sio_data->sio_inb(sio_data, 0xed); - - fan3pin = !(cr1c & BIT(5)); - fan4pin = !(cr1c & BIT(6)); - fan5pin = !(cr1c & BIT(7)); - - pwm3pin = !(cr1c & BIT(0)); - pwm4pin = !(cr1c & BIT(1)); - pwm5pin = !(cr1c & BIT(2)); - - switch (data->kind) { - case nct6791: - fan6pin = cr2d & BIT(1); - pwm6pin = cr2d & BIT(0); - break; - case nct6792: - fan6pin = !dsw_en && (cr2d & BIT(1)); - pwm6pin = !dsw_en && (cr2d & BIT(0)); - break; - case nct6793: - fan5pin |= cr1b & BIT(5); - fan5pin |= creb & BIT(5); - - fan6pin = !dsw_en && (cr2d & BIT(1)); - fan6pin |= creb & BIT(3); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = !dsw_en && (cr2d & BIT(0)); - pwm6pin |= creb & BIT(2); - break; - case nct6795: - fan5pin |= cr1b & BIT(5); - fan5pin |= creb & BIT(5); - - fan6pin = (cr2a & BIT(4)) && - (!dsw_en || (cred & BIT(4))); - fan6pin |= creb & BIT(3); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); - pwm6pin |= creb & BIT(2); - break; - case nct6796: - fan5pin |= cr1b & BIT(5); - fan5pin |= (cre0 & BIT(3)) && !(cr1b & BIT(0)); - fan5pin |= creb & BIT(5); - - fan6pin = (cr2a & BIT(4)) && - (!dsw_en || (cred & BIT(4))); - fan6pin |= creb & BIT(3); - - fan7pin = !(cr2b & BIT(2)); - - pwm5pin |= cr2d & BIT(7); - pwm5pin |= (cre0 & BIT(4)) && !(cr1b & BIT(0)); - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm6pin = (cr2a & BIT(3)) && (cred & BIT(2)); - pwm6pin |= creb & BIT(2); - - pwm7pin = !(cr1d & (BIT(2) | BIT(3))); - break; - case nct6797: - fan5pin |= !ddr4_en && (cr1b & BIT(5)); - fan5pin |= creb & BIT(5); - - fan6pin = cr2a & BIT(4); - fan6pin |= creb & BIT(3); - - fan7pin = cr1a & BIT(1); - - pwm5pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - pwm5pin |= !ddr4_en && (cr2d & BIT(7)); - - pwm6pin = creb & BIT(2); - pwm6pin |= cred & BIT(2); - - pwm7pin = cr1d & BIT(4); - break; - case nct6798: - fan6pin = !(cr1b & BIT(0)) && (cre0 & BIT(3)); - fan6pin |= cr2a & BIT(4); - fan6pin |= creb & BIT(5); - - fan7pin = cr1b & BIT(5); - fan7pin |= !(cr2b & BIT(2)); - fan7pin |= creb & BIT(3); - - pwm6pin = !(cr1b & BIT(0)) && (cre0 & BIT(4)); - pwm6pin |= !(cred & BIT(2)) && (cr2a & BIT(3)); - pwm6pin |= (creb & BIT(4)) && !(cr2a & BIT(0)); - - pwm7pin = !(cr1d & (BIT(2) | BIT(3))); - pwm7pin |= cr2d & BIT(7); - pwm7pin |= creb & BIT(2); - break; - default: /* NCT6779D */ - break; - } - - fan4min = fan4pin; - } - - /* fan 1 and 2 (0x03) are always present */ - data->has_fan = 0x03 | (fan3pin << 2) | (fan4pin << 3) | - (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); - data->has_fan_min = 0x03 | (fan3pin << 2) | (fan4min << 3) | - (fan5pin << 4) | (fan6pin << 5) | (fan7pin << 6); - data->has_pwm = 0x03 | (pwm3pin << 2) | (pwm4pin << 3) | - (pwm5pin << 4) | (pwm6pin << 5) | (pwm7pin << 6); -} - -static int add_temp_sensors(struct nct6775_data *data, const u16 *regp, - int *available, int *mask) -{ - int i, err; - u16 src; - - for (i = 0; i < data->pwm_num && *available; i++) { - int index; - - if (!regp[i]) - continue; - err = nct6775_read_value(data, regp[i], &src); - if (err) - return err; - src &= 0x1f; - if (!src || (*mask & BIT(src))) - continue; - if (!(data->temp_mask & BIT(src))) - continue; - - index = __ffs(*available); - err = nct6775_write_value(data, data->REG_TEMP_SOURCE[index], src); - if (err) - return err; - *available &= ~BIT(index); - *mask |= BIT(src); - } - - return 0; -} - -static const struct regmap_config nct6775_regmap_config = { - .reg_bits = 16, - .val_bits = 16, - .reg_read = nct6775_reg_read, - .reg_write = nct6775_reg_write, -}; - -static const struct regmap_config nct6775_wmi_regmap_config = { - .reg_bits = 16, - .val_bits = 16, - .reg_read = nct6775_wmi_reg_read, - .reg_write = nct6775_wmi_reg_write, -}; - -static int nct6775_probe(struct platform_device *pdev) -{ - struct device *dev = &pdev->dev; - struct nct6775_sio_data *sio_data = dev_get_platdata(dev); - struct nct6775_data *data; - struct resource *res; - int i, s, err = 0; - int mask, available; - u16 src; - const u16 *reg_temp, *reg_temp_over, *reg_temp_hyst, *reg_temp_config; - const u16 *reg_temp_mon, *reg_temp_alternate, *reg_temp_crit; - const u16 *reg_temp_crit_l = NULL, *reg_temp_crit_h = NULL; - int num_reg_temp, num_reg_temp_mon, num_reg_tsi_temp; - u8 cr2a; - struct device *hwmon_dev; - struct sensor_template_group tsi_temp_tg; - const struct regmap_config *regmapcfg; - - if (sio_data->access == access_direct) { - 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->sio_data = sio_data; - - if (sio_data->access == access_direct) { - data->addr = res->start; - regmapcfg = &nct6775_regmap_config; - } else { - regmapcfg = &nct6775_wmi_regmap_config; - } - - data->regmap = devm_regmap_init(dev, NULL, data, regmapcfg); - if (IS_ERR(data->regmap)) - return PTR_ERR(data->regmap); - - 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 nct6106: - data->in_num = 9; - data->pwm_num = 3; - data->auto_pwm_num = 4; - data->temp_fixed_num = 3; - data->num_temp_alarms = 6; - data->num_temp_beeps = 6; - - data->fan_from_reg = fan_from_reg13; - data->fan_from_reg_min = fan_from_reg13; - - data->temp_label = nct6776_temp_label; - data->temp_mask = NCT6776_TEMP_MASK; - data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; - - data->REG_VBAT = NCT6106_REG_VBAT; - data->REG_DIODE = NCT6106_REG_DIODE; - data->DIODE_MASK = NCT6106_DIODE_MASK; - data->REG_VIN = NCT6106_REG_IN; - data->REG_IN_MINMAX[0] = NCT6106_REG_IN_MIN; - data->REG_IN_MINMAX[1] = NCT6106_REG_IN_MAX; - data->REG_TARGET = NCT6106_REG_TARGET; - data->REG_FAN = NCT6106_REG_FAN; - data->REG_FAN_MODE = NCT6106_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6106_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6106_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6106_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6106_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6106_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6106_REG_FAN_STEP_DOWN_TIME; - data->REG_TOLERANCE_H = NCT6106_REG_TOLERANCE_H; - data->REG_PWM[0] = NCT6116_REG_PWM; - data->REG_PWM[1] = NCT6106_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6106_REG_FAN_STOP_OUTPUT; - data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; - data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; - data->REG_PWM_READ = NCT6106_REG_PWM_READ; - data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6106_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6106_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6106_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6106_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_CRITICAL_PWM_ENABLE = NCT6106_REG_CRITICAL_PWM_ENABLE; - data->CRITICAL_PWM_ENABLE_MASK - = NCT6106_CRITICAL_PWM_ENABLE_MASK; - data->REG_CRITICAL_PWM = NCT6106_REG_CRITICAL_PWM; - data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6106_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6106_REG_ALARM; - data->ALARM_BITS = NCT6106_ALARM_BITS; - data->REG_BEEP = NCT6106_REG_BEEP; - data->BEEP_BITS = NCT6106_BEEP_BITS; - data->REG_TSI_TEMP = NCT6106_REG_TSI_TEMP; - - reg_temp = NCT6106_REG_TEMP; - reg_temp_mon = NCT6106_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); - num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); - num_reg_tsi_temp = ARRAY_SIZE(NCT6106_REG_TSI_TEMP); - reg_temp_over = NCT6106_REG_TEMP_OVER; - reg_temp_hyst = NCT6106_REG_TEMP_HYST; - reg_temp_config = NCT6106_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6106_REG_TEMP_CRIT; - reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; - reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; - - break; - case nct6116: - data->in_num = 9; - data->pwm_num = 3; - data->auto_pwm_num = 4; - data->temp_fixed_num = 3; - data->num_temp_alarms = 3; - data->num_temp_beeps = 3; - - data->fan_from_reg = fan_from_reg13; - data->fan_from_reg_min = fan_from_reg13; - - data->temp_label = nct6776_temp_label; - data->temp_mask = NCT6776_TEMP_MASK; - data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; - - data->REG_VBAT = NCT6106_REG_VBAT; - data->REG_DIODE = NCT6106_REG_DIODE; - data->DIODE_MASK = NCT6106_DIODE_MASK; - data->REG_VIN = NCT6106_REG_IN; - data->REG_IN_MINMAX[0] = NCT6106_REG_IN_MIN; - data->REG_IN_MINMAX[1] = NCT6106_REG_IN_MAX; - data->REG_TARGET = NCT6116_REG_TARGET; - data->REG_FAN = NCT6116_REG_FAN; - data->REG_FAN_MODE = NCT6116_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6116_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6116_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6116_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6116_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6116_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6116_REG_FAN_STEP_DOWN_TIME; - data->REG_TOLERANCE_H = NCT6116_REG_TOLERANCE_H; - data->REG_PWM[0] = NCT6116_REG_PWM; - data->REG_PWM[1] = NCT6116_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6116_REG_FAN_STOP_OUTPUT; - data->REG_PWM[5] = NCT6106_REG_WEIGHT_DUTY_STEP; - data->REG_PWM[6] = NCT6106_REG_WEIGHT_DUTY_BASE; - data->REG_PWM_READ = NCT6106_REG_PWM_READ; - data->REG_PWM_MODE = NCT6106_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6106_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6116_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6116_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6116_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6116_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_CRITICAL_PWM_ENABLE = NCT6116_REG_CRITICAL_PWM_ENABLE; - data->CRITICAL_PWM_ENABLE_MASK - = NCT6106_CRITICAL_PWM_ENABLE_MASK; - data->REG_CRITICAL_PWM = NCT6116_REG_CRITICAL_PWM; - data->REG_TEMP_OFFSET = NCT6106_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6116_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6116_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6106_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6106_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6106_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6106_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6106_REG_ALARM; - data->ALARM_BITS = NCT6116_ALARM_BITS; - data->REG_BEEP = NCT6106_REG_BEEP; - data->BEEP_BITS = NCT6116_BEEP_BITS; - data->REG_TSI_TEMP = NCT6116_REG_TSI_TEMP; - - reg_temp = NCT6106_REG_TEMP; - reg_temp_mon = NCT6106_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6106_REG_TEMP); - num_reg_temp_mon = ARRAY_SIZE(NCT6106_REG_TEMP_MON); - num_reg_tsi_temp = ARRAY_SIZE(NCT6116_REG_TSI_TEMP); - reg_temp_over = NCT6106_REG_TEMP_OVER; - reg_temp_hyst = NCT6106_REG_TEMP_HYST; - reg_temp_config = NCT6106_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6106_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6106_REG_TEMP_CRIT; - reg_temp_crit_l = NCT6106_REG_TEMP_CRIT_L; - reg_temp_crit_h = NCT6106_REG_TEMP_CRIT_H; - - break; - case nct6775: - data->in_num = 9; - data->pwm_num = 3; - data->auto_pwm_num = 6; - data->has_fan_div = true; - data->temp_fixed_num = 3; - data->num_temp_alarms = 3; - data->num_temp_beeps = 3; - - data->ALARM_BITS = NCT6775_ALARM_BITS; - data->BEEP_BITS = NCT6775_BEEP_BITS; - - data->fan_from_reg = fan_from_reg16; - data->fan_from_reg_min = fan_from_reg8; - data->target_temp_mask = 0x7f; - data->tolerance_mask = 0x0f; - data->speed_tolerance_limit = 15; - - data->temp_label = nct6775_temp_label; - data->temp_mask = NCT6775_TEMP_MASK; - data->virt_temp_mask = NCT6775_VIRT_TEMP_MASK; - - data->REG_CONFIG = NCT6775_REG_CONFIG; - data->REG_VBAT = NCT6775_REG_VBAT; - data->REG_DIODE = NCT6775_REG_DIODE; - data->DIODE_MASK = NCT6775_DIODE_MASK; - 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_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = NCT6775_REG_FAN; - data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6775_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6775_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6775_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6775_REG_FAN_STEP_DOWN_TIME; - data->REG_PWM[0] = NCT6775_REG_PWM; - data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; - data->REG_PWM[3] = NCT6775_REG_FAN_MAX_OUTPUT; - data->REG_PWM[4] = NCT6775_REG_FAN_STEP_OUTPUT; - data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; - data->REG_PWM_READ = NCT6775_REG_PWM_READ; - data->REG_PWM_MODE = NCT6775_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6775_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6775_REG_ALARM; - data->REG_BEEP = NCT6775_REG_BEEP; - data->REG_TSI_TEMP = NCT6775_REG_TSI_TEMP; - - reg_temp = NCT6775_REG_TEMP; - reg_temp_mon = NCT6775_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); - num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); - num_reg_tsi_temp = ARRAY_SIZE(NCT6775_REG_TSI_TEMP); - reg_temp_over = NCT6775_REG_TEMP_OVER; - reg_temp_hyst = NCT6775_REG_TEMP_HYST; - reg_temp_config = NCT6775_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6775_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6775_REG_TEMP_CRIT; - - break; - case nct6776: - data->in_num = 9; - data->pwm_num = 3; - data->auto_pwm_num = 4; - data->has_fan_div = false; - data->temp_fixed_num = 3; - data->num_temp_alarms = 3; - data->num_temp_beeps = 6; - - data->ALARM_BITS = NCT6776_ALARM_BITS; - data->BEEP_BITS = NCT6776_BEEP_BITS; - - data->fan_from_reg = fan_from_reg13; - data->fan_from_reg_min = fan_from_reg13; - data->target_temp_mask = 0xff; - data->tolerance_mask = 0x07; - data->speed_tolerance_limit = 63; - - data->temp_label = nct6776_temp_label; - data->temp_mask = NCT6776_TEMP_MASK; - data->virt_temp_mask = NCT6776_VIRT_TEMP_MASK; - - data->REG_CONFIG = NCT6775_REG_CONFIG; - data->REG_VBAT = NCT6775_REG_VBAT; - data->REG_DIODE = NCT6775_REG_DIODE; - data->DIODE_MASK = NCT6775_DIODE_MASK; - 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_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = NCT6775_REG_FAN; - data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6776_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; - data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; - data->REG_PWM[0] = NCT6775_REG_PWM; - data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; - data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; - data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; - data->REG_PWM_READ = NCT6775_REG_PWM_READ; - data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_TEMP_OFFSET = NCT6775_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6775_REG_ALARM; - data->REG_BEEP = NCT6776_REG_BEEP; - data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; - - reg_temp = NCT6775_REG_TEMP; - reg_temp_mon = NCT6775_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6775_REG_TEMP); - num_reg_temp_mon = ARRAY_SIZE(NCT6775_REG_TEMP_MON); - num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); - reg_temp_over = NCT6775_REG_TEMP_OVER; - reg_temp_hyst = NCT6775_REG_TEMP_HYST; - reg_temp_config = NCT6776_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6776_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6776_REG_TEMP_CRIT; - - break; - case nct6779: - data->in_num = 15; - data->pwm_num = 5; - data->auto_pwm_num = 4; - data->has_fan_div = false; - data->temp_fixed_num = 6; - data->num_temp_alarms = 2; - data->num_temp_beeps = 2; - - data->ALARM_BITS = NCT6779_ALARM_BITS; - data->BEEP_BITS = NCT6779_BEEP_BITS; - - data->fan_from_reg = fan_from_reg_rpm; - data->fan_from_reg_min = fan_from_reg13; - data->target_temp_mask = 0xff; - data->tolerance_mask = 0x07; - data->speed_tolerance_limit = 63; - - data->temp_label = nct6779_temp_label; - data->temp_mask = NCT6779_TEMP_MASK; - data->virt_temp_mask = NCT6779_VIRT_TEMP_MASK; - - data->REG_CONFIG = NCT6775_REG_CONFIG; - data->REG_VBAT = NCT6775_REG_VBAT; - data->REG_DIODE = NCT6775_REG_DIODE; - data->DIODE_MASK = NCT6775_DIODE_MASK; - 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_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = NCT6779_REG_FAN; - data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; - data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; - data->REG_PWM[0] = NCT6775_REG_PWM; - data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; - data->REG_PWM[5] = NCT6775_REG_WEIGHT_DUTY_STEP; - data->REG_PWM[6] = NCT6776_REG_WEIGHT_DUTY_BASE; - data->REG_PWM_READ = NCT6775_REG_PWM_READ; - data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; - data->CRITICAL_PWM_ENABLE_MASK - = NCT6779_CRITICAL_PWM_ENABLE_MASK; - data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; - data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6775_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6775_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6775_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6775_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6779_REG_ALARM; - data->REG_BEEP = NCT6776_REG_BEEP; - data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; - - reg_temp = NCT6779_REG_TEMP; - reg_temp_mon = NCT6779_REG_TEMP_MON; - num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); - num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); - num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); - reg_temp_over = NCT6779_REG_TEMP_OVER; - reg_temp_hyst = NCT6779_REG_TEMP_HYST; - reg_temp_config = NCT6779_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6779_REG_TEMP_CRIT; - - break; - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - data->in_num = 15; - data->pwm_num = (data->kind == nct6796 || - data->kind == nct6797 || - data->kind == nct6798) ? 7 : 6; - data->auto_pwm_num = 4; - data->has_fan_div = false; - data->temp_fixed_num = 6; - data->num_temp_alarms = 2; - data->num_temp_beeps = 2; - - data->ALARM_BITS = NCT6791_ALARM_BITS; - data->BEEP_BITS = NCT6779_BEEP_BITS; - - data->fan_from_reg = fan_from_reg_rpm; - data->fan_from_reg_min = fan_from_reg13; - data->target_temp_mask = 0xff; - data->tolerance_mask = 0x07; - data->speed_tolerance_limit = 63; - - switch (data->kind) { - default: - case nct6791: - data->temp_label = nct6779_temp_label; - data->temp_mask = NCT6791_TEMP_MASK; - data->virt_temp_mask = NCT6791_VIRT_TEMP_MASK; - break; - case nct6792: - data->temp_label = nct6792_temp_label; - data->temp_mask = NCT6792_TEMP_MASK; - data->virt_temp_mask = NCT6792_VIRT_TEMP_MASK; - break; - case nct6793: - data->temp_label = nct6793_temp_label; - data->temp_mask = NCT6793_TEMP_MASK; - data->virt_temp_mask = NCT6793_VIRT_TEMP_MASK; - break; - case nct6795: - case nct6797: - data->temp_label = nct6795_temp_label; - data->temp_mask = NCT6795_TEMP_MASK; - data->virt_temp_mask = NCT6795_VIRT_TEMP_MASK; - break; - case nct6796: - data->temp_label = nct6796_temp_label; - data->temp_mask = NCT6796_TEMP_MASK; - data->virt_temp_mask = NCT6796_VIRT_TEMP_MASK; - break; - case nct6798: - data->temp_label = nct6798_temp_label; - data->temp_mask = NCT6798_TEMP_MASK; - data->virt_temp_mask = NCT6798_VIRT_TEMP_MASK; - break; - } - - data->REG_CONFIG = NCT6775_REG_CONFIG; - data->REG_VBAT = NCT6775_REG_VBAT; - data->REG_DIODE = NCT6775_REG_DIODE; - data->DIODE_MASK = NCT6775_DIODE_MASK; - 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_TARGET = NCT6775_REG_TARGET; - data->REG_FAN = NCT6779_REG_FAN; - data->REG_FAN_MODE = NCT6775_REG_FAN_MODE; - data->REG_FAN_MIN = NCT6776_REG_FAN_MIN; - data->REG_FAN_PULSES = NCT6779_REG_FAN_PULSES; - data->FAN_PULSE_SHIFT = NCT6775_FAN_PULSE_SHIFT; - data->REG_FAN_TIME[0] = NCT6775_REG_FAN_STOP_TIME; - data->REG_FAN_TIME[1] = NCT6776_REG_FAN_STEP_UP_TIME; - data->REG_FAN_TIME[2] = NCT6776_REG_FAN_STEP_DOWN_TIME; - data->REG_TOLERANCE_H = NCT6776_REG_TOLERANCE_H; - data->REG_PWM[0] = NCT6775_REG_PWM; - data->REG_PWM[1] = NCT6775_REG_FAN_START_OUTPUT; - data->REG_PWM[2] = NCT6775_REG_FAN_STOP_OUTPUT; - data->REG_PWM[5] = NCT6791_REG_WEIGHT_DUTY_STEP; - data->REG_PWM[6] = NCT6791_REG_WEIGHT_DUTY_BASE; - data->REG_PWM_READ = NCT6775_REG_PWM_READ; - data->REG_PWM_MODE = NCT6776_REG_PWM_MODE; - data->PWM_MODE_MASK = NCT6776_PWM_MODE_MASK; - data->REG_AUTO_TEMP = NCT6775_REG_AUTO_TEMP; - data->REG_AUTO_PWM = NCT6775_REG_AUTO_PWM; - data->REG_CRITICAL_TEMP = NCT6775_REG_CRITICAL_TEMP; - data->REG_CRITICAL_TEMP_TOLERANCE - = NCT6775_REG_CRITICAL_TEMP_TOLERANCE; - data->REG_CRITICAL_PWM_ENABLE = NCT6779_REG_CRITICAL_PWM_ENABLE; - data->CRITICAL_PWM_ENABLE_MASK - = NCT6779_CRITICAL_PWM_ENABLE_MASK; - data->REG_CRITICAL_PWM = NCT6779_REG_CRITICAL_PWM; - data->REG_TEMP_OFFSET = NCT6779_REG_TEMP_OFFSET; - data->REG_TEMP_SOURCE = NCT6775_REG_TEMP_SOURCE; - data->REG_TEMP_SEL = NCT6775_REG_TEMP_SEL; - data->REG_WEIGHT_TEMP_SEL = NCT6791_REG_WEIGHT_TEMP_SEL; - data->REG_WEIGHT_TEMP[0] = NCT6791_REG_WEIGHT_TEMP_STEP; - data->REG_WEIGHT_TEMP[1] = NCT6791_REG_WEIGHT_TEMP_STEP_TOL; - data->REG_WEIGHT_TEMP[2] = NCT6791_REG_WEIGHT_TEMP_BASE; - data->REG_ALARM = NCT6791_REG_ALARM; - if (data->kind == nct6791) - data->REG_BEEP = NCT6776_REG_BEEP; - else - data->REG_BEEP = NCT6792_REG_BEEP; - switch (data->kind) { - case nct6791: - case nct6792: - case nct6793: - data->REG_TSI_TEMP = NCT6776_REG_TSI_TEMP; - num_reg_tsi_temp = ARRAY_SIZE(NCT6776_REG_TSI_TEMP); - break; - case nct6795: - case nct6796: - case nct6797: - case nct6798: - data->REG_TSI_TEMP = NCT6796_REG_TSI_TEMP; - num_reg_tsi_temp = ARRAY_SIZE(NCT6796_REG_TSI_TEMP); - break; - default: - num_reg_tsi_temp = 0; - break; - } - - reg_temp = NCT6779_REG_TEMP; - num_reg_temp = ARRAY_SIZE(NCT6779_REG_TEMP); - if (data->kind == nct6791) { - reg_temp_mon = NCT6779_REG_TEMP_MON; - num_reg_temp_mon = ARRAY_SIZE(NCT6779_REG_TEMP_MON); - } else { - reg_temp_mon = NCT6792_REG_TEMP_MON; - num_reg_temp_mon = ARRAY_SIZE(NCT6792_REG_TEMP_MON); - } - reg_temp_over = NCT6779_REG_TEMP_OVER; - reg_temp_hyst = NCT6779_REG_TEMP_HYST; - reg_temp_config = NCT6779_REG_TEMP_CONFIG; - reg_temp_alternate = NCT6779_REG_TEMP_ALTERNATE; - reg_temp_crit = NCT6779_REG_TEMP_CRIT; - - break; - default: - return -ENODEV; - } - data->have_in = BIT(data->in_num) - 1; - data->have_temp = 0; - - /* - * On some boards, not all available temperature sources are monitored, - * even though some of the monitoring registers are unused. - * Get list of unused monitoring registers, then detect if any fan - * controls are configured to use unmonitored temperature sources. - * If so, assign the unmonitored temperature sources to available - * monitoring registers. - */ - mask = 0; - available = 0; - for (i = 0; i < num_reg_temp; i++) { - if (reg_temp[i] == 0) - continue; - - err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); - if (err) - return err; - src &= 0x1f; - if (!src || (mask & BIT(src))) - available |= BIT(i); - - mask |= BIT(src); - } - - /* - * Now find unmonitored temperature registers and enable monitoring - * if additional monitoring registers are available. - */ - err = add_temp_sensors(data, data->REG_TEMP_SEL, &available, &mask); - if (err) - return err; - err = add_temp_sensors(data, data->REG_WEIGHT_TEMP_SEL, &available, &mask); - if (err) - return err; - - mask = 0; - s = NUM_TEMP_FIXED; /* First dynamic temperature attribute */ - for (i = 0; i < num_reg_temp; i++) { - if (reg_temp[i] == 0) - continue; - - err = nct6775_read_value(data, data->REG_TEMP_SOURCE[i], &src); - if (err) - return err; - src &= 0x1f; - if (!src || (mask & BIT(src))) - continue; - - if (!(data->temp_mask & BIT(src))) { - dev_info(dev, - "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", - src, i, data->REG_TEMP_SOURCE[i], reg_temp[i]); - continue; - } - - mask |= BIT(src); - - /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ - if (src <= data->temp_fixed_num) { - data->have_temp |= BIT(src - 1); - data->have_temp_fixed |= BIT(src - 1); - data->reg_temp[0][src - 1] = reg_temp[i]; - data->reg_temp[1][src - 1] = reg_temp_over[i]; - data->reg_temp[2][src - 1] = reg_temp_hyst[i]; - if (reg_temp_crit_h && reg_temp_crit_h[i]) - data->reg_temp[3][src - 1] = reg_temp_crit_h[i]; - else if (reg_temp_crit[src - 1]) - data->reg_temp[3][src - 1] - = reg_temp_crit[src - 1]; - if (reg_temp_crit_l && reg_temp_crit_l[i]) - data->reg_temp[4][src - 1] = reg_temp_crit_l[i]; - data->reg_temp_config[src - 1] = reg_temp_config[i]; - data->temp_src[src - 1] = src; - continue; - } - - if (s >= NUM_TEMP) - continue; - - /* Use dynamic index for other sources */ - data->have_temp |= BIT(s); - data->reg_temp[0][s] = reg_temp[i]; - data->reg_temp[1][s] = reg_temp_over[i]; - data->reg_temp[2][s] = reg_temp_hyst[i]; - data->reg_temp_config[s] = reg_temp_config[i]; - if (reg_temp_crit_h && reg_temp_crit_h[i]) - data->reg_temp[3][s] = reg_temp_crit_h[i]; - else if (reg_temp_crit[src - 1]) - data->reg_temp[3][s] = reg_temp_crit[src - 1]; - if (reg_temp_crit_l && reg_temp_crit_l[i]) - data->reg_temp[4][s] = reg_temp_crit_l[i]; - - data->temp_src[s] = src; - s++; - } - - /* - * Repeat with temperatures used for fan control. - * This set of registers does not support limits. - */ - for (i = 0; i < num_reg_temp_mon; i++) { - if (reg_temp_mon[i] == 0) - continue; - - err = nct6775_read_value(data, data->REG_TEMP_SEL[i], &src); - if (err) - return err; - src &= 0x1f; - if (!src) - continue; - - if (!(data->temp_mask & BIT(src))) { - dev_info(dev, - "Invalid temperature source %d at index %d, source register 0x%x, temp register 0x%x\n", - src, i, data->REG_TEMP_SEL[i], - reg_temp_mon[i]); - continue; - } - - /* - * For virtual temperature sources, the 'virtual' temperature - * for each fan reflects a different temperature, and there - * are no duplicates. - */ - if (!(data->virt_temp_mask & BIT(src))) { - if (mask & BIT(src)) - continue; - mask |= BIT(src); - } - - /* Use fixed index for SYSTIN(1), CPUTIN(2), AUXTIN(3) */ - if (src <= data->temp_fixed_num) { - if (data->have_temp & BIT(src - 1)) - continue; - data->have_temp |= BIT(src - 1); - data->have_temp_fixed |= BIT(src - 1); - data->reg_temp[0][src - 1] = reg_temp_mon[i]; - data->temp_src[src - 1] = src; - continue; - } - - if (s >= NUM_TEMP) - continue; - - /* Use dynamic index for other sources */ - data->have_temp |= BIT(s); - data->reg_temp[0][s] = reg_temp_mon[i]; - data->temp_src[s] = src; - s++; - } - -#ifdef USE_ALTERNATE - /* - * Go through the list of alternate temp registers and enable - * if possible. - * The temperature is already monitored if the respective bit in - * is set. - */ - for (i = 0; i < 31; i++) { - if (!(data->temp_mask & BIT(i + 1))) - continue; - if (!reg_temp_alternate[i]) - continue; - if (mask & BIT(i + 1)) - continue; - if (i < data->temp_fixed_num) { - if (data->have_temp & BIT(i)) - continue; - data->have_temp |= BIT(i); - data->have_temp_fixed |= BIT(i); - data->reg_temp[0][i] = reg_temp_alternate[i]; - if (i < num_reg_temp) { - data->reg_temp[1][i] = reg_temp_over[i]; - data->reg_temp[2][i] = reg_temp_hyst[i]; - } - data->temp_src[i] = i + 1; - continue; - } - - if (s >= NUM_TEMP) /* Abort if no more space */ - break; - - data->have_temp |= BIT(s); - data->reg_temp[0][s] = reg_temp_alternate[i]; - data->temp_src[s] = i + 1; - s++; - } -#endif /* USE_ALTERNATE */ - - /* Check which TSIx_TEMP registers are active */ - for (i = 0; i < num_reg_tsi_temp; i++) { - u16 tmp; - - err = nct6775_read_value(data, data->REG_TSI_TEMP[i], &tmp); - if (err) - return err; - if (tmp) - data->have_tsi_temp |= BIT(i); - } - - /* Initialize the chip */ - err = nct6775_init_device(data); - if (err) - return err; - - err = sio_data->sio_enter(sio_data); - if (err) - return err; - - cr2a = sio_data->sio_inb(sio_data, 0x2a); - switch (data->kind) { - case nct6775: - data->have_vid = (cr2a & 0x40); - break; - case nct6776: - data->have_vid = (cr2a & 0x60) == 0x40; - break; - case nct6106: - case nct6116: - case nct6779: - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - break; - } - - /* - * Read VID value - * We can get the VID input values directly at logical device D 0xe3. - */ - if (data->have_vid) { - sio_data->sio_select(sio_data, NCT6775_LD_VID); - data->vid = sio_data->sio_inb(sio_data, 0xe3); - data->vrm = vid_which_vrm(); - } - - if (fan_debounce) { - u8 tmp; - - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - tmp = sio_data->sio_inb(sio_data, - NCT6775_REG_CR_FAN_DEBOUNCE); - switch (data->kind) { - case nct6106: - case nct6116: - tmp |= 0xe0; - break; - case nct6775: - tmp |= 0x1e; - break; - case nct6776: - case nct6779: - tmp |= 0x3e; - break; - case nct6791: - case nct6792: - case nct6793: - case nct6795: - case nct6796: - case nct6797: - case nct6798: - tmp |= 0x7e; - break; - } - sio_data->sio_outb(sio_data, NCT6775_REG_CR_FAN_DEBOUNCE, - tmp); - dev_info(&pdev->dev, "Enabled fan debounce for chip %s\n", - data->name); - } - - nct6775_check_fan_inputs(data, sio_data); - - sio_data->sio_exit(sio_data); - - /* Read fan clock dividers immediately */ - err = nct6775_init_fan_common(dev, data); - if (err) - return err; - - /* Register sysfs hooks */ - err = nct6775_add_template_attr_group(dev, data, &nct6775_pwm_template_group, - data->pwm_num); - if (err) - return err; - - err = nct6775_add_template_attr_group(dev, data, &nct6775_in_template_group, - fls(data->have_in)); - if (err) - return err; - - err = nct6775_add_template_attr_group(dev, data, &nct6775_fan_template_group, - fls(data->has_fan)); - if (err) - return err; - - err = nct6775_add_template_attr_group(dev, data, &nct6775_temp_template_group, - fls(data->have_temp)); - if (err) - return err; - - if (data->have_tsi_temp) { - tsi_temp_tg.templates = nct6775_tsi_temp_template; - tsi_temp_tg.is_visible = nct6775_tsi_temp_is_visible; - tsi_temp_tg.base = fls(data->have_temp) + 1; - err = nct6775_add_template_attr_group(dev, data, &tsi_temp_tg, - fls(data->have_tsi_temp)); - if (err) - return err; - } - - err = nct6775_add_attr_group(data, &nct6775_group_other); - if (err) - return err; - - hwmon_dev = devm_hwmon_device_register_with_groups(dev, data->name, - data, data->groups); - return PTR_ERR_OR_ZERO(hwmon_dev); -} - -static void nct6791_enable_io_mapping(struct nct6775_sio_data *sio_data) -{ - int val; - - val = sio_data->sio_inb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); - if (val & 0x10) { - pr_info("Enabling hardware monitor logical device mappings.\n"); - sio_data->sio_outb(sio_data, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, - val & ~0x10); - } -} - -static int __maybe_unused nct6775_suspend(struct device *dev) -{ - int err; - u16 tmp; - struct nct6775_data *data = nct6775_update_device(dev); - - if (IS_ERR(data)) - return PTR_ERR(data); - - mutex_lock(&data->update_lock); - err = nct6775_read_value(data, data->REG_VBAT, &tmp); - if (err) - goto out; - data->vbat = tmp; - if (data->kind == nct6775) { - err = nct6775_read_value(data, NCT6775_REG_FANDIV1, &tmp); - if (err) - goto out; - data->fandiv1 = tmp; - - err = nct6775_read_value(data, NCT6775_REG_FANDIV2, &tmp); - if (err) - goto out; - data->fandiv2 = tmp; - } -out: - mutex_unlock(&data->update_lock); - - return err; -} - -static int __maybe_unused nct6775_resume(struct device *dev) -{ - struct nct6775_data *data = dev_get_drvdata(dev); - struct nct6775_sio_data *sio_data = dev_get_platdata(dev); - int i, j, err = 0; - u8 reg; - - mutex_lock(&data->update_lock); - data->bank = 0xff; /* Force initial bank selection */ - - err = sio_data->sio_enter(sio_data); - if (err) - goto abort; - - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - reg = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - if (reg != data->sio_reg_enable) - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, data->sio_reg_enable); - - if (data->kind == nct6791 || data->kind == nct6792 || - data->kind == nct6793 || data->kind == nct6795 || - data->kind == nct6796 || data->kind == nct6797 || - data->kind == nct6798) - nct6791_enable_io_mapping(sio_data); - - sio_data->sio_exit(sio_data); - - /* Restore limits */ - for (i = 0; i < data->in_num; i++) { - if (!(data->have_in & BIT(i))) - continue; - - err = nct6775_write_value(data, data->REG_IN_MINMAX[0][i], data->in[i][1]); - if (err) - goto abort; - err = nct6775_write_value(data, data->REG_IN_MINMAX[1][i], data->in[i][2]); - if (err) - goto abort; - } - - for (i = 0; i < ARRAY_SIZE(data->fan_min); i++) { - if (!(data->has_fan_min & BIT(i))) - continue; - - err = nct6775_write_value(data, data->REG_FAN_MIN[i], data->fan_min[i]); - if (err) - goto abort; - } - - for (i = 0; i < NUM_TEMP; i++) { - if (!(data->have_temp & BIT(i))) - continue; - - for (j = 1; j < ARRAY_SIZE(data->reg_temp); j++) - if (data->reg_temp[j][i]) { - err = nct6775_write_temp(data, data->reg_temp[j][i], - data->temp[j][i]); - if (err) - goto abort; - } - } - - /* Restore other settings */ - err = nct6775_write_value(data, data->REG_VBAT, data->vbat); - if (err) - goto abort; - if (data->kind == nct6775) { - err = nct6775_write_value(data, NCT6775_REG_FANDIV1, data->fandiv1); - if (err) - goto abort; - err = nct6775_write_value(data, NCT6775_REG_FANDIV2, data->fandiv2); - } - -abort: - /* Force re-reading all values */ - data->valid = false; - mutex_unlock(&data->update_lock); - - return err; -} - -static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); - -static struct platform_driver nct6775_driver = { - .driver = { - .name = DRVNAME, - .pm = &nct6775_dev_pm_ops, - }, - .probe = nct6775_probe, -}; - -/* nct6775_find() looks for a '627 in the Super-I/O config space */ -static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) -{ - u16 val; - int err; - int addr; - - sio_data->access = access_direct; - sio_data->sioreg = sioaddr; - - err = sio_data->sio_enter(sio_data); - if (err) - return err; - - val = (sio_data->sio_inb(sio_data, SIO_REG_DEVID) << 8) | - sio_data->sio_inb(sio_data, SIO_REG_DEVID + 1); - if (force_id && val != 0xffff) - val = force_id; - - switch (val & SIO_ID_MASK) { - case SIO_NCT6106_ID: - sio_data->kind = nct6106; - break; - case SIO_NCT6116_ID: - sio_data->kind = nct6116; - break; - case SIO_NCT6775_ID: - sio_data->kind = nct6775; - break; - case SIO_NCT6776_ID: - sio_data->kind = nct6776; - break; - case SIO_NCT6779_ID: - sio_data->kind = nct6779; - break; - case SIO_NCT6791_ID: - sio_data->kind = nct6791; - break; - case SIO_NCT6792_ID: - sio_data->kind = nct6792; - break; - case SIO_NCT6793_ID: - sio_data->kind = nct6793; - break; - case SIO_NCT6795_ID: - sio_data->kind = nct6795; - break; - case SIO_NCT6796_ID: - sio_data->kind = nct6796; - break; - case SIO_NCT6797_ID: - sio_data->kind = nct6797; - break; - case SIO_NCT6798_ID: - sio_data->kind = nct6798; - break; - default: - if (val != 0xffff) - pr_debug("unsupported chip ID: 0x%04x\n", val); - sio_data->sio_exit(sio_data); - return -ENODEV; - } - - /* We have a known chip, find the HWM I/O address */ - sio_data->sio_select(sio_data, NCT6775_LD_HWM); - val = (sio_data->sio_inb(sio_data, SIO_REG_ADDR) << 8) - | sio_data->sio_inb(sio_data, 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"); - sio_data->sio_exit(sio_data); - return -ENODEV; - } - - /* Activate logical device if needed */ - val = sio_data->sio_inb(sio_data, SIO_REG_ENABLE); - if (!(val & 0x01)) { - pr_warn("Forcibly enabling Super-I/O. Sensor is probably unusable.\n"); - sio_data->sio_outb(sio_data, SIO_REG_ENABLE, val | 0x01); - } - - if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || - sio_data->kind == nct6793 || sio_data->kind == nct6795 || - sio_data->kind == nct6796 || sio_data->kind == nct6797 || - sio_data->kind == nct6798) - nct6791_enable_io_mapping(sio_data); - - sio_data->sio_exit(sio_data); - pr_info("Found %s or compatible chip at %#x:%#x\n", - nct6775_sio_names[sio_data->kind], sioaddr, addr); - - return addr; -} - -/* - * 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 use platform_device_alloc(), we - * must keep track of the device - */ -static struct platform_device *pdev[2]; - -static const char * const asus_wmi_boards[] = { - "ProArt X570-CREATOR WIFI", - "Pro B550M-C", - "Pro WS X570-ACE", - "PRIME B360-PLUS", - "PRIME B460-PLUS", - "PRIME B550-PLUS", - "PRIME B550M-A", - "PRIME B550M-A (WI-FI)", - "PRIME X570-P", - "PRIME X570-PRO", - "ROG CROSSHAIR VIII DARK HERO", - "ROG CROSSHAIR VIII FORMULA", - "ROG CROSSHAIR VIII HERO", - "ROG CROSSHAIR VIII IMPACT", - "ROG STRIX B550-A GAMING", - "ROG STRIX B550-E GAMING", - "ROG STRIX B550-F GAMING", - "ROG STRIX B550-F GAMING (WI-FI)", - "ROG STRIX B550-F GAMING WIFI II", - "ROG STRIX B550-I GAMING", - "ROG STRIX B550-XE GAMING (WI-FI)", - "ROG STRIX X570-E GAMING", - "ROG STRIX X570-F GAMING", - "ROG STRIX X570-I GAMING", - "ROG STRIX Z390-E GAMING", - "ROG STRIX Z390-F GAMING", - "ROG STRIX Z390-H GAMING", - "ROG STRIX Z390-I GAMING", - "ROG STRIX Z490-A GAMING", - "ROG STRIX Z490-E GAMING", - "ROG STRIX Z490-F GAMING", - "ROG STRIX Z490-G GAMING", - "ROG STRIX Z490-G GAMING (WI-FI)", - "ROG STRIX Z490-H GAMING", - "ROG STRIX Z490-I GAMING", - "TUF GAMING B550M-PLUS", - "TUF GAMING B550M-PLUS (WI-FI)", - "TUF GAMING B550-PLUS", - "TUF GAMING B550-PRO", - "TUF GAMING X570-PLUS", - "TUF GAMING X570-PLUS (WI-FI)", - "TUF GAMING X570-PRO (WI-FI)", - "TUF GAMING Z490-PLUS", - "TUF GAMING Z490-PLUS (WI-FI)", -}; - -static int __init sensors_nct6775_init(void) -{ - int i, err; - bool found = false; - int address; - struct resource res; - struct nct6775_sio_data sio_data; - int sioaddr[2] = { 0x2e, 0x4e }; - enum sensor_access access = access_direct; - const char *board_vendor, *board_name; - u8 tmp; - - err = platform_driver_register(&nct6775_driver); - if (err) - return err; - - board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); - board_name = dmi_get_system_info(DMI_BOARD_NAME); - - if (board_name && board_vendor && - !strcmp(board_vendor, "ASUSTeK COMPUTER INC.")) { - err = match_string(asus_wmi_boards, ARRAY_SIZE(asus_wmi_boards), - board_name); - if (err >= 0) { - /* if reading chip id via WMI succeeds, use WMI */ - if (!nct6775_asuswmi_read(0, NCT6775_PORT_CHIPID, &tmp) && tmp) { - pr_info("Using Asus WMI to access %#x chip.\n", tmp); - access = access_asuswmi; - } else { - pr_err("Can't read ChipID by Asus WMI.\n"); - } - } - } - - /* - * 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() - */ - for (i = 0; i < ARRAY_SIZE(pdev); i++) { - sio_data.sio_outb = superio_outb; - sio_data.sio_inb = superio_inb; - sio_data.sio_select = superio_select; - sio_data.sio_enter = superio_enter; - sio_data.sio_exit = superio_exit; - - address = nct6775_find(sioaddr[i], &sio_data); - if (address <= 0) - continue; - - found = true; - - sio_data.access = access; - - if (access == access_asuswmi) { - sio_data.sio_outb = superio_wmi_outb; - sio_data.sio_inb = superio_wmi_inb; - sio_data.sio_select = superio_wmi_select; - sio_data.sio_enter = superio_wmi_enter; - sio_data.sio_exit = superio_wmi_exit; - } - - 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 nct6775_sio_data)); - if (err) - goto exit_device_put; - - if (sio_data.access == access_direct) { - 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(&nct6775_driver); - return err; -} - -static void __exit sensors_nct6775_exit(void) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(pdev); i++) { - if (pdev[i]) - platform_device_unregister(pdev[i]); - } - platform_driver_unregister(&nct6775_driver); -} - -MODULE_AUTHOR("Guenter Roeck "); -MODULE_DESCRIPTION("Driver for NCT6775F and compatible chips"); -MODULE_LICENSE("GPL"); - -module_init(sensors_nct6775_init); -module_exit(sensors_nct6775_exit); diff --git a/drivers/hwmon/nct6775.h b/drivers/hwmon/nct6775.h new file mode 100644 index 000000000000..93f708148e65 --- /dev/null +++ b/drivers/hwmon/nct6775.h @@ -0,0 +1,252 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef __HWMON_NCT6775_H__ +#define __HWMON_NCT6775_H__ + +#include + +enum kinds { nct6106, nct6116, nct6775, nct6776, nct6779, nct6791, nct6792, + nct6793, nct6795, nct6796, nct6797, nct6798 }; +enum pwm_enable { off, manual, thermal_cruise, speed_cruise, sf3, sf4 }; + +#define NUM_TEMP 10 /* Max number of temp attribute sets w/ limits*/ +#define NUM_TEMP_FIXED 6 /* Max number of fixed temp attribute sets */ +#define NUM_TSI_TEMP 8 /* Max number of TSI temp register pairs */ + +#define NUM_REG_ALARM 7 /* Max number of alarm registers */ +#define NUM_REG_BEEP 5 /* Max number of beep registers */ + +#define NUM_FAN 7 + +struct nct6775_data { + int addr; /* IO base of hw monitor block */ + int sioreg; /* SIO register address */ + enum kinds kind; + const char *name; + + const struct attribute_group *groups[7]; + u8 num_groups; + + u16 reg_temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit + */ + u8 temp_src[NUM_TEMP]; + u16 reg_temp_config[NUM_TEMP]; + const char * const *temp_label; + u32 temp_mask; + u32 virt_temp_mask; + + u16 REG_CONFIG; + u16 REG_VBAT; + u16 REG_DIODE; + u8 DIODE_MASK; + + const s8 *ALARM_BITS; + const s8 *BEEP_BITS; + + const u16 *REG_VIN; + const u16 *REG_IN_MINMAX[2]; + + const u16 *REG_TARGET; + const u16 *REG_FAN; + const u16 *REG_FAN_MODE; + const u16 *REG_FAN_MIN; + const u16 *REG_FAN_PULSES; + const u16 *FAN_PULSE_SHIFT; + const u16 *REG_FAN_TIME[3]; + + const u16 *REG_TOLERANCE_H; + + const u8 *REG_PWM_MODE; + const u8 *PWM_MODE_MASK; + + const u16 *REG_PWM[7]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + const u16 *REG_PWM_READ; + + const u16 *REG_CRITICAL_PWM_ENABLE; + u8 CRITICAL_PWM_ENABLE_MASK; + const u16 *REG_CRITICAL_PWM; + + const u16 *REG_AUTO_TEMP; + const u16 *REG_AUTO_PWM; + + const u16 *REG_CRITICAL_TEMP; + const u16 *REG_CRITICAL_TEMP_TOLERANCE; + + const u16 *REG_TEMP_SOURCE; /* temp register sources */ + const u16 *REG_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP_SEL; + const u16 *REG_WEIGHT_TEMP[3]; /* 0=base, 1=tolerance, 2=step */ + + const u16 *REG_TEMP_OFFSET; + + const u16 *REG_ALARM; + const u16 *REG_BEEP; + + const u16 *REG_TSI_TEMP; + + unsigned int (*fan_from_reg)(u16 reg, unsigned int divreg); + unsigned int (*fan_from_reg_min)(u16 reg, unsigned int divreg); + + 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 */ + unsigned int rpm[NUM_FAN]; + u16 fan_min[NUM_FAN]; + u8 fan_pulses[NUM_FAN]; + u8 fan_div[NUM_FAN]; + u8 has_pwm; + u8 has_fan; /* some fan inputs can be disabled */ + u8 has_fan_min; /* some fans don't have min register */ + bool has_fan_div; + + u8 num_temp_alarms; /* 2, 3, or 6 */ + u8 num_temp_beeps; /* 2, 3, or 6 */ + u8 temp_fixed_num; /* 3 or 6 */ + u8 temp_type[NUM_TEMP_FIXED]; + s8 temp_offset[NUM_TEMP_FIXED]; + s16 temp[5][NUM_TEMP]; /* 0=temp, 1=temp_over, 2=temp_hyst, + * 3=temp_crit, 4=temp_lcrit + */ + s16 tsi_temp[NUM_TSI_TEMP]; + u64 alarms; + u64 beeps; + + u8 pwm_num; /* number of pwm */ + u8 pwm_mode[NUM_FAN]; /* 0->DC variable voltage, + * 1->PWM variable duty cycle + */ + enum pwm_enable pwm_enable[NUM_FAN]; + /* 0->off + * 1->manual + * 2->thermal cruise mode (also called SmartFan I) + * 3->fan speed cruise mode + * 4->SmartFan III + * 5->enhanced variable thermal cruise (SmartFan IV) + */ + u8 pwm[7][NUM_FAN]; /* [0]=pwm, [1]=pwm_start, [2]=pwm_floor, + * [3]=pwm_max, [4]=pwm_step, + * [5]=weight_duty_step, [6]=weight_duty_base + */ + + u8 target_temp[NUM_FAN]; + u8 target_temp_mask; + u32 target_speed[NUM_FAN]; + u32 target_speed_tolerance[NUM_FAN]; + u8 speed_tolerance_limit; + + u8 temp_tolerance[2][NUM_FAN]; + u8 tolerance_mask; + + u8 fan_time[3][NUM_FAN]; /* 0 = stop_time, 1 = step_up, 2 = step_down */ + + /* Automatic fan speed control registers */ + int auto_pwm_num; + u8 auto_pwm[NUM_FAN][7]; + u8 auto_temp[NUM_FAN][7]; + u8 pwm_temp_sel[NUM_FAN]; + u8 pwm_weight_temp_sel[NUM_FAN]; + u8 weight_temp[3][NUM_FAN]; /* 0->temp_step, 1->temp_step_tol, + * 2->temp_base + */ + + u8 vid; + u8 vrm; + + bool have_vid; + + u16 have_temp; + u16 have_temp_fixed; + u16 have_tsi_temp; + u16 have_in; + + /* Remember extra register values over suspend/resume */ + u8 vbat; + u8 fandiv1; + u8 fandiv2; + u8 sio_reg_enable; + + struct regmap *regmap; + bool read_only; + + /* driver-specific (platform, i2c) initialization hook and data */ + int (*driver_init)(struct nct6775_data *data); + void *driver_data; +}; + +static inline int nct6775_read_value(struct nct6775_data *data, u16 reg, u16 *value) +{ + unsigned int tmp; + int ret = regmap_read(data->regmap, reg, &tmp); + + if (!ret) + *value = tmp; + return ret; +} + +static inline int nct6775_write_value(struct nct6775_data *data, u16 reg, u16 value) +{ + return regmap_write(data->regmap, reg, value); +} + +bool nct6775_reg_is_word_sized(struct nct6775_data *data, u16 reg); +int nct6775_probe(struct device *dev, struct nct6775_data *data, + const struct regmap_config *regmapcfg); + +ssize_t nct6775_show_alarm(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t nct6775_show_beep(struct device *dev, struct device_attribute *attr, char *buf); +ssize_t nct6775_store_beep(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count); + +static inline int nct6775_write_temp(struct nct6775_data *data, u16 reg, u16 value) +{ + if (!nct6775_reg_is_word_sized(data, reg)) + value >>= 8; + return nct6775_write_value(data, reg, value); +} + +static inline umode_t nct6775_attr_mode(struct nct6775_data *data, struct attribute *attr) +{ + return data->read_only ? (attr->mode & ~0222) : attr->mode; +} + +static inline int +nct6775_add_attr_group(struct nct6775_data *data, const struct attribute_group *group) +{ + /* Need to leave a NULL terminator at the end of data->groups */ + if (data->num_groups == ARRAY_SIZE(data->groups) - 1) + return -ENOBUFS; + + data->groups[data->num_groups++] = group; + return 0; +} + +#define NCT6775_REG_BANK 0x4E +#define NCT6775_REG_CONFIG 0x40 + +#define NCT6775_REG_FANDIV1 0x506 +#define NCT6775_REG_FANDIV2 0x507 + +#define NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE 0x28 + +#define FAN_ALARM_BASE 16 +#define TEMP_ALARM_BASE 24 +#define INTRUSION_ALARM_BASE 30 +#define BEEP_ENABLE_BASE 15 + +/* + * 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 + */ + +#endif /* __HWMON_NCT6775_H__ */ -- cgit v1.2.3 From 849b0156d9960da628a06756bb920d9571c15e66 Mon Sep 17 00:00:00 2001 From: Eddie James Date: Wed, 27 Apr 2022 09:04:43 -0500 Subject: hwmon: (occ) Delay hwmon registration until user request Instead of registering the hwmon device at probe time, use the existing "occ_active" sysfs file to control when the driver polls the OCC for sensor data and registers with hwmon. The reason for this change is that the SBE, which is the device by which the driver communicates with the OCC, cannot handle communications during certain system state transitions, resulting in unrecoverable system errors. Signed-off-by: Eddie James Link: https://lore.kernel.org/r/20220427140443.11428-1-eajames@linux.ibm.com Signed-off-by: Guenter Roeck --- drivers/hwmon/occ/common.c | 100 +++++++++++++++++++++++---------- drivers/hwmon/occ/common.h | 5 +- drivers/hwmon/occ/p8_i2c.c | 2 +- drivers/hwmon/occ/p9_sbe.c | 2 +- drivers/hwmon/occ/sysfs.c | 137 ++++++++++++++++++++++++++------------------- 5 files changed, 156 insertions(+), 90 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c index f00cd59f1d19..d78f4bebc718 100644 --- a/drivers/hwmon/occ/common.c +++ b/drivers/hwmon/occ/common.c @@ -1149,44 +1149,75 @@ static void occ_parse_poll_response(struct occ *occ) sizeof(*header), size + sizeof(*header)); } -int occ_setup(struct occ *occ, const char *name) +int occ_active(struct occ *occ, bool active) { - int rc; - - mutex_init(&occ->lock); - occ->groups[0] = &occ->group; + int rc = mutex_lock_interruptible(&occ->lock); - /* no need to lock */ - rc = occ_poll(occ); - if (rc == -ESHUTDOWN) { - dev_info(occ->bus_dev, "host is not ready\n"); - return rc; - } else if (rc < 0) { - dev_err(occ->bus_dev, - "failed to get OCC poll response=%02x: %d\n", - occ->resp.return_status, rc); + if (rc) return rc; - } - occ->next_update = jiffies + OCC_UPDATE_FREQUENCY; - occ_parse_poll_response(occ); + if (active) { + if (occ->active) { + rc = -EALREADY; + goto unlock; + } - rc = occ_setup_sensor_attrs(occ); - if (rc) { - dev_err(occ->bus_dev, "failed to setup sensor attrs: %d\n", - rc); - return rc; - } + occ->error_count = 0; + occ->last_safe = 0; - occ->hwmon = devm_hwmon_device_register_with_groups(occ->bus_dev, name, - occ, occ->groups); - if (IS_ERR(occ->hwmon)) { - rc = PTR_ERR(occ->hwmon); - dev_err(occ->bus_dev, "failed to register hwmon device: %d\n", - rc); - return rc; + rc = occ_poll(occ); + if (rc < 0) { + dev_err(occ->bus_dev, + "failed to get OCC poll response=%02x: %d\n", + occ->resp.return_status, rc); + goto unlock; + } + + occ->active = true; + occ->next_update = jiffies + OCC_UPDATE_FREQUENCY; + occ_parse_poll_response(occ); + + rc = occ_setup_sensor_attrs(occ); + if (rc) { + dev_err(occ->bus_dev, + "failed to setup sensor attrs: %d\n", rc); + goto unlock; + } + + occ->hwmon = hwmon_device_register_with_groups(occ->bus_dev, + "occ", occ, + occ->groups); + if (IS_ERR(occ->hwmon)) { + rc = PTR_ERR(occ->hwmon); + occ->hwmon = NULL; + dev_err(occ->bus_dev, + "failed to register hwmon device: %d\n", rc); + goto unlock; + } + } else { + if (!occ->active) { + rc = -EALREADY; + goto unlock; + } + + if (occ->hwmon) + hwmon_device_unregister(occ->hwmon); + occ->active = false; + occ->hwmon = NULL; } +unlock: + mutex_unlock(&occ->lock); + return rc; +} + +int occ_setup(struct occ *occ) +{ + int rc; + + mutex_init(&occ->lock); + occ->groups[0] = &occ->group; + rc = occ_setup_sysfs(occ); if (rc) dev_err(occ->bus_dev, "failed to setup sysfs: %d\n", rc); @@ -1195,6 +1226,15 @@ int occ_setup(struct occ *occ, const char *name) } EXPORT_SYMBOL_GPL(occ_setup); +void occ_shutdown(struct occ *occ) +{ + occ_shutdown_sysfs(occ); + + if (occ->hwmon) + hwmon_device_unregister(occ->hwmon); +} +EXPORT_SYMBOL_GPL(occ_shutdown); + MODULE_AUTHOR("Eddie James "); MODULE_DESCRIPTION("Common OCC hwmon code"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/occ/common.h b/drivers/hwmon/occ/common.h index 2dd4a4d240c0..64d5ec7e169b 100644 --- a/drivers/hwmon/occ/common.h +++ b/drivers/hwmon/occ/common.h @@ -106,6 +106,7 @@ struct occ { struct attribute_group group; const struct attribute_group *groups[2]; + bool active; int error; /* final transfer error after retry */ int last_error; /* latest transfer error */ unsigned int error_count; /* number of xfr errors observed */ @@ -123,9 +124,11 @@ struct occ { u8 prev_mode; }; -int occ_setup(struct occ *occ, const char *name); +int occ_active(struct occ *occ, bool active); +int occ_setup(struct occ *occ); int occ_setup_sysfs(struct occ *occ); void occ_shutdown(struct occ *occ); +void occ_shutdown_sysfs(struct occ *occ); void occ_sysfs_poll_done(struct occ *occ); int occ_update_response(struct occ *occ); diff --git a/drivers/hwmon/occ/p8_i2c.c b/drivers/hwmon/occ/p8_i2c.c index 9e61e1fb5142..da39ea28df31 100644 --- a/drivers/hwmon/occ/p8_i2c.c +++ b/drivers/hwmon/occ/p8_i2c.c @@ -223,7 +223,7 @@ static int p8_i2c_occ_probe(struct i2c_client *client) occ->poll_cmd_data = 0x10; /* P8 OCC poll data */ occ->send_cmd = p8_i2c_occ_send_cmd; - return occ_setup(occ, "p8_occ"); + return occ_setup(occ); } static int p8_i2c_occ_remove(struct i2c_client *client) diff --git a/drivers/hwmon/occ/p9_sbe.c b/drivers/hwmon/occ/p9_sbe.c index 49b13cc01073..42fc7b97bb34 100644 --- a/drivers/hwmon/occ/p9_sbe.c +++ b/drivers/hwmon/occ/p9_sbe.c @@ -145,7 +145,7 @@ static int p9_sbe_occ_probe(struct platform_device *pdev) occ->poll_cmd_data = 0x20; /* P9 OCC poll data */ occ->send_cmd = p9_sbe_occ_send_cmd; - rc = occ_setup(occ, "p9_occ"); + rc = occ_setup(occ); if (rc == -ESHUTDOWN) rc = -ENODEV; /* Host is shutdown, don't spew errors */ diff --git a/drivers/hwmon/occ/sysfs.c b/drivers/hwmon/occ/sysfs.c index b2f788a77746..2317301fc1e9 100644 --- a/drivers/hwmon/occ/sysfs.c +++ b/drivers/hwmon/occ/sysfs.c @@ -6,13 +6,13 @@ #include #include #include +#include #include #include "common.h" /* OCC status register */ #define OCC_STAT_MASTER BIT(7) -#define OCC_STAT_ACTIVE BIT(0) /* OCC extended status register */ #define OCC_EXT_STAT_DVFS_OT BIT(7) @@ -22,6 +22,25 @@ #define OCC_EXT_STAT_DVFS_VDD BIT(3) #define OCC_EXT_STAT_GPU_THROTTLE GENMASK(2, 0) +static ssize_t occ_active_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int rc; + bool active; + struct occ *occ = dev_get_drvdata(dev); + + rc = kstrtobool(buf, &active); + if (rc) + return rc; + + rc = occ_active(occ, active); + if (rc) + return rc; + + return count; +} + static ssize_t occ_sysfs_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -31,54 +50,64 @@ static ssize_t occ_sysfs_show(struct device *dev, struct occ_poll_response_header *header; struct sensor_device_attribute *sattr = to_sensor_dev_attr(attr); - rc = occ_update_response(occ); - if (rc) - return rc; + if (occ->active) { + rc = occ_update_response(occ); + if (rc) + return rc; - header = (struct occ_poll_response_header *)occ->resp.data; - - switch (sattr->index) { - case 0: - val = !!(header->status & OCC_STAT_MASTER); - break; - case 1: - val = !!(header->status & OCC_STAT_ACTIVE); - break; - case 2: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); - break; - case 3: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); - break; - case 4: - val = !!(header->ext_status & OCC_EXT_STAT_MEM_THROTTLE); - break; - case 5: - val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); - break; - case 6: - val = header->occ_state; - break; - case 7: - if (header->status & OCC_STAT_MASTER) - val = hweight8(header->occs_present); - else + header = (struct occ_poll_response_header *)occ->resp.data; + + switch (sattr->index) { + case 0: + val = !!(header->status & OCC_STAT_MASTER); + break; + case 1: val = 1; - break; - case 8: - val = header->ips_status; - break; - case 9: - val = header->mode; - break; - case 10: - val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); - break; - case 11: - val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; - break; - default: - return -EINVAL; + break; + case 2: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_OT); + break; + case 3: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_POWER); + break; + case 4: + val = !!(header->ext_status & + OCC_EXT_STAT_MEM_THROTTLE); + break; + case 5: + val = !!(header->ext_status & OCC_EXT_STAT_QUICK_DROP); + break; + case 6: + val = header->occ_state; + break; + case 7: + if (header->status & OCC_STAT_MASTER) + val = hweight8(header->occs_present); + else + val = 1; + break; + case 8: + val = header->ips_status; + break; + case 9: + val = header->mode; + break; + case 10: + val = !!(header->ext_status & OCC_EXT_STAT_DVFS_VDD); + break; + case 11: + val = header->ext_status & OCC_EXT_STAT_GPU_THROTTLE; + break; + default: + return -EINVAL; + } + } else { + if (sattr->index == 1) + val = 0; + else if (sattr->index <= 11) + val = -ENODATA; + else + return -EINVAL; } return sysfs_emit(buf, "%d\n", val); @@ -95,7 +124,8 @@ static ssize_t occ_error_show(struct device *dev, } static SENSOR_DEVICE_ATTR(occ_master, 0444, occ_sysfs_show, NULL, 0); -static SENSOR_DEVICE_ATTR(occ_active, 0444, occ_sysfs_show, NULL, 1); +static SENSOR_DEVICE_ATTR(occ_active, 0644, occ_sysfs_show, occ_active_store, + 1); static SENSOR_DEVICE_ATTR(occ_dvfs_overtemp, 0444, occ_sysfs_show, NULL, 2); static SENSOR_DEVICE_ATTR(occ_dvfs_power, 0444, occ_sysfs_show, NULL, 3); static SENSOR_DEVICE_ATTR(occ_mem_throttle, 0444, occ_sysfs_show, NULL, 4); @@ -139,7 +169,7 @@ void occ_sysfs_poll_done(struct occ *occ) * On the first poll response, we haven't yet created the sysfs * attributes, so don't make any notify calls. */ - if (!occ->hwmon) + if (!occ->active) goto done; if ((header->status & OCC_STAT_MASTER) != @@ -148,12 +178,6 @@ void occ_sysfs_poll_done(struct occ *occ) sysfs_notify(&occ->bus_dev->kobj, NULL, name); } - if ((header->status & OCC_STAT_ACTIVE) != - (occ->prev_stat & OCC_STAT_ACTIVE)) { - name = sensor_dev_attr_occ_active.dev_attr.attr.name; - sysfs_notify(&occ->bus_dev->kobj, NULL, name); - } - if ((header->ext_status & OCC_EXT_STAT_DVFS_OT) != (occ->prev_ext_stat & OCC_EXT_STAT_DVFS_OT)) { name = sensor_dev_attr_occ_dvfs_overtemp.dev_attr.attr.name; @@ -227,8 +251,7 @@ int occ_setup_sysfs(struct occ *occ) return sysfs_create_group(&occ->bus_dev->kobj, &occ_sysfs); } -void occ_shutdown(struct occ *occ) +void occ_shutdown_sysfs(struct occ *occ) { sysfs_remove_group(&occ->bus_dev->kobj, &occ_sysfs); } -EXPORT_SYMBOL_GPL(occ_shutdown); -- cgit v1.2.3 From 5cd29012028d997f46518dae0a8133e0985713f3 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 27 Apr 2022 16:29:58 +0200 Subject: hwmon: (asus-ec-sensors) introduce ec_board_info struct for board data We need to keep some more information about the current board than just the sensors set, and with more boards to add the dmi id array grows quickly. Our probe code is always the same so let's switch to a custom test code and a custom board info array. That allows us to omit board vendor string (ASUS uses two strings that differ in case) in the board info and use case-insensitive comparison, and also do not duplicate sensor definitions for such board variants as " (WI-FI)" when sensors are identical to the base variant. Also saves a quarter of the module size by replacing big dmi_system_id structs with smaller ones. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220427143001.1443605-2-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 212 +++++++++++++++++++++++----------------- 1 file changed, 123 insertions(+), 89 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index e3d794fb0534..0c82723f85b5 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -54,8 +54,7 @@ static char *mutex_path_override; /* ACPI mutex for locking access to the EC for the firmware */ #define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" -/* There are two variants of the vendor spelling */ -#define VENDOR_ASUS_UPPER_CASE "ASUSTeK COMPUTER INC." +#define MAX_IDENTICAL_BOARD_VARIATIONS 2 typedef union { u32 value; @@ -164,74 +163,94 @@ static const struct ec_sensor_info known_ec_sensors[] = { (SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB) #define SENSOR_SET_TEMP_WATER (SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT) -#define DMI_EXACT_MATCH_BOARD(vendor, name, sensors) { \ - .matches = { \ - DMI_EXACT_MATCH(DMI_BOARD_VENDOR, vendor), \ - DMI_EXACT_MATCH(DMI_BOARD_NAME, name), \ - }, \ - .driver_data = (void *)(sensors), \ -} +struct ec_board_info { + const char *board_names[MAX_IDENTICAL_BOARD_VARIATIONS]; + unsigned long sensors; +}; -static const struct dmi_system_id asus_ec_dmi_table[] __initconst = { - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "PRIME X570-PRO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ProArt X570-CREATOR WIFI", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "Pro WS X570-ACE", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII DARK HERO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII FORMULA", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG CROSSHAIR VIII HERO", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII HERO (WI-FI)", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, - "ROG CROSSHAIR VIII IMPACT", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-E GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX B550-I GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-E GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-F GAMING", - SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET), - DMI_EXACT_MATCH_BOARD(VENDOR_ASUS_UPPER_CASE, "ROG STRIX X570-I GAMING", - SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | - SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE), +static const struct ec_board_info board_info[] = { + { + .board_names = {"PRIME X570-PRO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + }, + { + .board_names = {"ProArt X570-CREATOR WIFI"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"Pro WS X570-ACE"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG CROSSHAIR VIII DARK HERO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG CROSSHAIR VIII FORMULA"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + }, + { + .board_names = { + "ROG CROSSHAIR VIII HERO", + "ROG CROSSHAIR VIII HERO (WI-FI)", + }, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG CROSSHAIR VIII IMPACT"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG STRIX B550-E GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + }, + { + .board_names = {"ROG STRIX B550-I GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG STRIX X570-E GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + }, + { + .board_names = {"ROG STRIX X570-F GAMING"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + }, + { + .board_names = {"ROG STRIX X570-I GAMING"}, + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | + SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + }, {} }; @@ -241,7 +260,7 @@ struct ec_sensor { }; struct ec_sensors_data { - unsigned long board_sensors; + const struct ec_board_info *board_info; struct ec_sensor *sensors; /* EC registers to read from */ u16 *registers; @@ -307,11 +326,6 @@ static int __init bank_compare(const void *a, const void *b) return *((const s8 *)a) - *((const s8 *)b); } -static int __init board_sensors_count(unsigned long sensors) -{ - return hweight_long(sensors); -} - static void __init setup_sensor_data(struct ec_sensors_data *ec) { struct ec_sensor *s = ec->sensors; @@ -322,8 +336,8 @@ static void __init setup_sensor_data(struct ec_sensors_data *ec) ec->nr_banks = 0; ec->nr_registers = 0; - for_each_set_bit(i, &ec->board_sensors, - BITS_PER_TYPE(ec->board_sensors)) { + for_each_set_bit(i, &ec->board_info->sensors, + BITS_PER_TYPE(ec->board_info->sensors)) { s->info_index = i; s->cached_value = 0; ec->nr_registers += @@ -463,9 +477,10 @@ static inline s32 get_sensor_value(const struct ec_sensor_info *si, u8 *data) static void update_sensor_values(struct ec_sensors_data *ec, u8 *data) { const struct ec_sensor_info *si; - struct ec_sensor *s; + struct ec_sensor *s, *sensor_end; - for (s = ec->sensors; s != ec->sensors + ec->nr_sensors; s++) { + sensor_end = ec->sensors + ec->nr_sensors; + for (s = ec->sensors; s != sensor_end; s++) { si = &known_ec_sensors[s->info_index]; s->cached_value = get_sensor_value(si, data); data += si->addr.components.size; @@ -603,12 +618,24 @@ static struct hwmon_chip_info asus_ec_chip_info = { .ops = &asus_ec_hwmon_ops, }; -static unsigned long __init get_board_sensors(void) +static const struct ec_board_info * __init get_board_info(void) { - const struct dmi_system_id *dmi_entry = - dmi_first_match(asus_ec_dmi_table); + const char *dmi_board_vendor = dmi_get_system_info(DMI_BOARD_VENDOR); + const char *dmi_board_name = dmi_get_system_info(DMI_BOARD_NAME); + const struct ec_board_info *board; - return dmi_entry ? (unsigned long)dmi_entry->driver_data : 0; + if (!dmi_board_vendor || !dmi_board_name || + strcasecmp(dmi_board_vendor, "ASUSTeK COMPUTER INC.")) + return NULL; + + for (board = board_info; board->sensors; board++) { + if (match_string(board->board_names, + MAX_IDENTICAL_BOARD_VARIATIONS, + dmi_board_name) >= 0) + return board; + } + + return NULL; } static int __init asus_ec_probe(struct platform_device *pdev) @@ -616,17 +643,17 @@ static int __init asus_ec_probe(struct platform_device *pdev) const struct hwmon_channel_info **ptr_asus_ec_ci; int nr_count[hwmon_max] = { 0 }, nr_types = 0; struct hwmon_channel_info *asus_ec_hwmon_chan; + const struct ec_board_info *pboard_info; const struct hwmon_chip_info *chip_info; struct device *dev = &pdev->dev; struct ec_sensors_data *ec_data; const struct ec_sensor_info *si; enum hwmon_sensor_types type; - unsigned long board_sensors; struct device *hwdev; unsigned int i; - board_sensors = get_board_sensors(); - if (!board_sensors) + pboard_info = get_board_info(); + if (!pboard_info) return -ENODEV; ec_data = devm_kzalloc(dev, sizeof(struct ec_sensors_data), @@ -635,8 +662,8 @@ static int __init asus_ec_probe(struct platform_device *pdev) return -ENOMEM; dev_set_drvdata(dev, ec_data); - ec_data->board_sensors = board_sensors; - ec_data->nr_sensors = board_sensors_count(ec_data->board_sensors); + ec_data->board_info = pboard_info; + ec_data->nr_sensors = hweight_long(ec_data->board_info->sensors); ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, sizeof(struct ec_sensor), GFP_KERNEL); @@ -709,7 +736,14 @@ static struct platform_driver asus_ec_sensors_platform_driver = { }, }; -MODULE_DEVICE_TABLE(dmi, asus_ec_dmi_table); +MODULE_DEVICE_TABLE(acpi, acpi_ec_ids); +/* + * we use module_platform_driver_probe() rather than module_platform_driver() + * because the probe function (and its dependants) are marked with __init, which + * means we can't put it into the .probe member of the platform_driver struct + * above, and we can't mark the asus_ec_sensors_platform_driver object as __init + * because the object is referenced from the module exit code. + */ module_platform_driver_probe(asus_ec_sensors_platform_driver, asus_ec_probe); module_param_named(mutex_path, mutex_path_override, charp, 0); -- cgit v1.2.3 From de8fbac5e59e239b00cdac611784b1bc1ff53d14 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 27 Apr 2022 16:29:59 +0200 Subject: hwmon: (asus-ec-sensors) implement locking via the ACPI global lock For some board models ASUS uses the global ACPI lock to guard access to the hardware, so do we. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220427143001.1443605-3-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 2 + drivers/hwmon/asus-ec-sensors.c | 125 +++++++++++++++++++++++++------- 2 files changed, 101 insertions(+), 26 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index b3469851ab9a..36ca531d32dd 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -53,3 +53,5 @@ Module Parameters the path is mostly identical for them). If ASUS changes this path in a future BIOS update, this parameter can be used to override the stored in the driver value until it gets updated. + A special string ":GLOBAL_LOCK" can be passed to use the ACPI + global lock instead of a dedicated mutex. diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 0c82723f85b5..581df4053060 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -56,6 +56,9 @@ static char *mutex_path_override; #define MAX_IDENTICAL_BOARD_VARIATIONS 2 +/* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */ +#define ACPI_GLOBAL_LOCK_PSEUDO_PATH ":GLOBAL_LOCK" + typedef union { u32 value; struct { @@ -166,6 +169,14 @@ static const struct ec_sensor_info known_ec_sensors[] = { struct ec_board_info { const char *board_names[MAX_IDENTICAL_BOARD_VARIATIONS]; unsigned long sensors; + /* + * Defines which mutex to use for guarding access to the state and the + * hardware. Can be either a full path to an AML mutex or the + * pseudo-path ACPI_GLOBAL_LOCK_PSEUDO_PATH to use the global ACPI lock, + * or left empty to use a regular mutex object, in which case access to + * the hardware is not guarded. + */ + const char *mutex_path; }; static const struct ec_board_info board_info[] = { @@ -173,6 +184,7 @@ static const struct ec_board_info board_info[] = { .board_names = {"PRIME X570-PRO"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ProArt X570-CREATOR WIFI"}, @@ -185,6 +197,7 @@ static const struct ec_board_info board_info[] = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG CROSSHAIR VIII DARK HERO"}, @@ -193,6 +206,7 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG CROSSHAIR VIII FORMULA"}, @@ -200,6 +214,7 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = { @@ -212,6 +227,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG CROSSHAIR VIII IMPACT"}, @@ -219,12 +235,14 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG STRIX B550-E GAMING"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG STRIX B550-I GAMING"}, @@ -232,6 +250,7 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG STRIX X570-E GAMING"}, @@ -239,17 +258,20 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG STRIX X570-F GAMING"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, { .board_names = {"ROG STRIX X570-I GAMING"}, .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_FAN_VRM_HS | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, }, {} }; @@ -259,6 +281,46 @@ struct ec_sensor { s32 cached_value; }; +struct lock_data { + union { + acpi_handle aml; + /* global lock handle */ + u32 glk; + } mutex; + bool (*lock)(struct lock_data *data); + bool (*unlock)(struct lock_data *data); +}; + +/* + * The next function pairs implement options for locking access to the + * state and the EC + */ +static bool lock_via_acpi_mutex(struct lock_data *data) +{ + /* + * ASUS DSDT does not specify that access to the EC has to be guarded, + * but firmware does access it via ACPI + */ + return ACPI_SUCCESS(acpi_acquire_mutex(data->mutex.aml, + NULL, ACPI_LOCK_DELAY_MS)); +} + +static bool unlock_acpi_mutex(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_release_mutex(data->mutex.aml, NULL)); +} + +static bool lock_via_global_acpi_lock(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_acquire_global_lock(ACPI_LOCK_DELAY_MS, + &data->mutex.glk)); +} + +static bool unlock_global_acpi_lock(struct lock_data *data) +{ + return ACPI_SUCCESS(acpi_release_global_lock(data->mutex.glk)); +} + struct ec_sensors_data { const struct ec_board_info *board_info; struct ec_sensor *sensors; @@ -269,7 +331,7 @@ struct ec_sensors_data { u8 banks[ASUS_EC_MAX_BANK + 1]; /* in jiffies */ unsigned long last_updated; - acpi_handle aml_mutex; + struct lock_data lock_data; /* number of board EC sensors */ u8 nr_sensors; /* @@ -373,23 +435,36 @@ static void __init fill_ec_registers(struct ec_sensors_data *ec) } } -static acpi_handle __init asus_hw_access_mutex(struct device *dev) +static int __init setup_lock_data(struct device *dev) { const char *mutex_path; - acpi_handle res; int status; + struct ec_sensors_data *state = dev_get_drvdata(dev); mutex_path = mutex_path_override ? - mutex_path_override : ASUS_HW_ACCESS_MUTEX_ASMX; + mutex_path_override : state->board_info->mutex_path; - status = acpi_get_handle(NULL, (acpi_string)mutex_path, &res); - if (ACPI_FAILURE(status)) { - dev_err(dev, - "Could not get hardware access guard mutex '%s': error %d", - mutex_path, status); - return NULL; + if (!mutex_path || !strlen(mutex_path)) { + dev_err(dev, "Hardware access guard mutex name is empty"); + return -EINVAL; } - return res; + if (!strcmp(mutex_path, ACPI_GLOBAL_LOCK_PSEUDO_PATH)) { + state->lock_data.mutex.glk = 0; + state->lock_data.lock = lock_via_global_acpi_lock; + state->lock_data.unlock = unlock_global_acpi_lock; + } else { + status = acpi_get_handle(NULL, (acpi_string)mutex_path, + &state->lock_data.mutex.aml); + if (ACPI_FAILURE(status)) { + dev_err(dev, + "Failed to get hardware access guard AML mutex '%s': error %d", + mutex_path, status); + return -ENOENT; + } + state->lock_data.lock = lock_via_acpi_mutex; + state->lock_data.unlock = unlock_acpi_mutex; + } + return 0; } static int asus_ec_bank_switch(u8 bank, u8 *old) @@ -492,15 +567,9 @@ static int update_ec_sensors(const struct device *dev, { int status; - /* - * ASUS DSDT does not specify that access to the EC has to be guarded, - * but firmware does access it via ACPI - */ - if (ACPI_FAILURE(acpi_acquire_mutex(ec->aml_mutex, NULL, - ACPI_LOCK_DELAY_MS))) { - dev_err(dev, "Failed to acquire AML mutex"); - status = -EBUSY; - goto cleanup; + if (!ec->lock_data.lock(&ec->lock_data)) { + dev_warn(dev, "Failed to acquire mutex"); + return -EBUSY; } status = asus_ec_block_read(dev, ec); @@ -508,10 +577,10 @@ static int update_ec_sensors(const struct device *dev, if (!status) { update_sensor_values(ec, ec->read_buffer); } - if (ACPI_FAILURE(acpi_release_mutex(ec->aml_mutex, NULL))) { - dev_err(dev, "Failed to release AML mutex"); - } -cleanup: + + if (!ec->lock_data.unlock(&ec->lock_data)) + dev_err(dev, "Failed to release mutex"); + return status; } @@ -651,6 +720,7 @@ static int __init asus_ec_probe(struct platform_device *pdev) enum hwmon_sensor_types type; struct device *hwdev; unsigned int i; + int status; pboard_info = get_board_info(); if (!pboard_info) @@ -667,6 +737,11 @@ static int __init asus_ec_probe(struct platform_device *pdev) ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, sizeof(struct ec_sensor), GFP_KERNEL); + status = setup_lock_data(dev); + if (status) { + dev_err(dev, "Failed to setup state/EC locking: %d", status); + return status; + } setup_sensor_data(ec_data); ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers, sizeof(u16), GFP_KERNEL); @@ -678,8 +753,6 @@ static int __init asus_ec_probe(struct platform_device *pdev) fill_ec_registers(ec_data); - ec_data->aml_mutex = asus_hw_access_mutex(dev); - for (i = 0; i < ec_data->nr_sensors; ++i) { si = get_sensor_info(ec_data, i); if (!nr_count[si->type]) -- cgit v1.2.3 From 45934e4af6736dc14c5641e25666b6a6a9d009d9 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 27 Apr 2022 16:30:00 +0200 Subject: hwmon: (asus-ec-sensors) add support for board families DSDT code for AMD 400-series chipset shows that sensor addresses differ for this generation from those for the AMD 500-series boards. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220427143001.1443605-4-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 581df4053060..109c3920d77c 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -135,8 +135,13 @@ enum ec_sensors { #define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in) #define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out) +enum board_family { + family_unknown, + family_amd_500_series, +}; + /* All the known sensors for ASUS EC controllers */ -static const struct ec_sensor_info known_ec_sensors[] = { +static const struct ec_sensor_info sensors_family_amd_500[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), [ec_sensor_temp_cpu] = EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), @@ -177,6 +182,7 @@ struct ec_board_info { * the hardware is not guarded. */ const char *mutex_path; + enum board_family family; }; static const struct ec_board_info board_info[] = { @@ -185,6 +191,7 @@ static const struct ec_board_info board_info[] = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ProArt X570-CREATOR WIFI"}, @@ -198,6 +205,7 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG CROSSHAIR VIII DARK HERO"}, @@ -207,6 +215,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CPU_OPT | SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG CROSSHAIR VIII FORMULA"}, @@ -215,6 +224,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = { @@ -228,6 +238,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG CROSSHAIR VIII IMPACT"}, @@ -236,6 +247,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG STRIX B550-E GAMING"}, @@ -243,6 +255,7 @@ static const struct ec_board_info board_info[] = { SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG STRIX B550-I GAMING"}, @@ -251,6 +264,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_VRM_HS | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG STRIX X570-E GAMING"}, @@ -259,12 +273,14 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG STRIX X570-F GAMING"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, { .board_names = {"ROG STRIX X570-I GAMING"}, @@ -272,6 +288,7 @@ static const struct ec_board_info board_info[] = { SENSOR_FAN_CHIPSET | SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, }, {} }; @@ -323,6 +340,7 @@ static bool unlock_global_acpi_lock(struct lock_data *data) struct ec_sensors_data { const struct ec_board_info *board_info; + const struct ec_sensor_info *sensors_info; struct ec_sensor *sensors; /* EC registers to read from */ u16 *registers; @@ -365,7 +383,7 @@ static bool is_sensor_data_signed(const struct ec_sensor_info *si) static const struct ec_sensor_info * get_sensor_info(const struct ec_sensors_data *state, int index) { - return &known_ec_sensors[state->sensors[index].info_index]; + return state->sensors_info + state->sensors[index].info_index; } static int find_ec_sensor_index(const struct ec_sensors_data *ec, @@ -403,9 +421,9 @@ static void __init setup_sensor_data(struct ec_sensors_data *ec) s->info_index = i; s->cached_value = 0; ec->nr_registers += - known_ec_sensors[s->info_index].addr.components.size; + ec->sensors_info[s->info_index].addr.components.size; bank_found = false; - bank = known_ec_sensors[s->info_index].addr.components.bank; + bank = ec->sensors_info[s->info_index].addr.components.bank; for (j = 0; j < ec->nr_banks; j++) { if (ec->banks[j] == bank) { bank_found = true; @@ -556,7 +574,7 @@ static void update_sensor_values(struct ec_sensors_data *ec, u8 *data) sensor_end = ec->sensors + ec->nr_sensors; for (s = ec->sensors; s != sensor_end; s++) { - si = &known_ec_sensors[s->info_index]; + si = ec->sensors_info + s->info_index; s->cached_value = get_sensor_value(si, data); data += si->addr.components.size; } @@ -733,6 +751,17 @@ static int __init asus_ec_probe(struct platform_device *pdev) dev_set_drvdata(dev, ec_data); ec_data->board_info = pboard_info; + + switch (ec_data->board_info->family) { + case family_amd_500_series: + ec_data->sensors_info = sensors_family_amd_500; + break; + default: + dev_err(dev, "Unknown board family: %d", + ec_data->board_info->family); + return -EINVAL; + } + ec_data->nr_sensors = hweight_long(ec_data->board_info->sensors); ec_data->sensors = devm_kcalloc(dev, ec_data->nr_sensors, sizeof(struct ec_sensor), GFP_KERNEL); @@ -742,6 +771,7 @@ static int __init asus_ec_probe(struct platform_device *pdev) dev_err(dev, "Failed to setup state/EC locking: %d", status); return status; } + setup_sensor_data(ec_data); ec_data->registers = devm_kcalloc(dev, ec_data->nr_registers, sizeof(u16), GFP_KERNEL); -- cgit v1.2.3 From 7cc44e5a45a69274071297579d3e6afeef24cc65 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 27 Apr 2022 16:30:01 +0200 Subject: hwmon: (asus-ec-sensors) add PRIME X470-PRO board This board is supposed to be handled by the asus-wmi-sensors driver, but due to a buggy WMI implementation the driver and the official ASUS software make the BIOS hang together with fan controls [1, 2]. This driver complements values provided by the SIO chip and does not freeze the BIOS, as tested by a user [2]. [1] https://github.com/electrified/asus-wmi-sensors/blob/master/README.md [2] https://github.com/zeule/asus-ec-sensors/issues/12 Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220427143001.1443605-5-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 109c3920d77c..998d49d6b799 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -137,10 +137,41 @@ enum ec_sensors { enum board_family { family_unknown, + family_amd_400_series, family_amd_500_series, }; /* All the known sensors for ASUS EC controllers */ +static const struct ec_sensor_info sensors_family_amd_400[] = { + [ec_sensor_temp_chipset] = + EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), + [ec_sensor_temp_cpu] = + EC_SENSOR("CPU", hwmon_temp, 1, 0x00, 0x3b), + [ec_sensor_temp_mb] = + EC_SENSOR("Motherboard", hwmon_temp, 1, 0x00, 0x3c), + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x00, 0x3d), + [ec_sensor_temp_vrm] = + EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x3e), + [ec_sensor_in_cpu_core] = + EC_SENSOR("CPU Core", hwmon_in, 2, 0x00, 0xa2), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc), + [ec_sensor_fan_vrm_hs] = + EC_SENSOR("VRM HS", hwmon_fan, 2, 0x00, 0xb2), + [ec_sensor_fan_chipset] = + /* no chipset fans in this generation */ + EC_SENSOR("Chipset", hwmon_fan, 0, 0x00, 0x00), + [ec_sensor_fan_water_flow] = + EC_SENSOR("Water_Flow", hwmon_fan, 2, 0x00, 0xb4), + [ec_sensor_curr_cpu] = + EC_SENSOR("CPU", hwmon_curr, 1, 0x00, 0xf4), + [ec_sensor_temp_water_in] = + EC_SENSOR("Water_In", hwmon_temp, 1, 0x01, 0x0d), + [ec_sensor_temp_water_out] = + EC_SENSOR("Water_Out", hwmon_temp, 1, 0x01, 0x0b), +}; + static const struct ec_sensor_info sensors_family_amd_500[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), @@ -186,6 +217,15 @@ struct ec_board_info { }; static const struct ec_board_info board_info[] = { + { + .board_names = {"PRIME X470-PRO"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT | + SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_400_series, + }, { .board_names = {"PRIME X570-PRO"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | @@ -753,6 +793,9 @@ static int __init asus_ec_probe(struct platform_device *pdev) ec_data->board_info = pboard_info; switch (ec_data->board_info->family) { + case family_amd_400_series: + ec_data->sensors_info = sensors_family_amd_400; + break; case family_amd_500_series: ec_data->sensors_info = sensors_family_amd_500; break; -- cgit v1.2.3 From 5de3e13f7f6b496bd7bd9ff4d2b915b7d3e67cda Mon Sep 17 00:00:00 2001 From: Mårten Lindahl Date: Thu, 28 Apr 2022 16:40:36 +0200 Subject: hwmon: (pmbus) Introduce and use write_byte_data callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the pmbus core functions uses pmbus_write_byte_data, which does not support driver callbacks for chip specific write operations. This could potentially influence some specific regulator chips that for example need a time delay before each data access. Lets add support for driver callback with _pmbus_write_byte_data. Signed-off-by: Mårten Lindahl Link: https://lore.kernel.org/r/20220428144039.2464667-2-marten.lindahl@axis.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus.h | 2 ++ drivers/hwmon/pmbus/pmbus_core.c | 24 +++++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/pmbus.h b/drivers/hwmon/pmbus/pmbus.h index e74b6ef070f3..c031a9700ace 100644 --- a/drivers/hwmon/pmbus/pmbus.h +++ b/drivers/hwmon/pmbus/pmbus.h @@ -438,6 +438,8 @@ struct pmbus_driver_info { int (*read_byte_data)(struct i2c_client *client, int page, int reg); int (*read_word_data)(struct i2c_client *client, int page, int phase, int reg); + int (*write_byte_data)(struct i2c_client *client, int page, int reg, + u8 byte); int (*write_word_data)(struct i2c_client *client, int page, int reg, u16 word); int (*write_byte)(struct i2c_client *client, int page, u8 value); diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index d93574d6a1fb..e7235d526e99 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -276,6 +276,24 @@ static int _pmbus_write_word_data(struct i2c_client *client, int page, int reg, return pmbus_write_word_data(client, page, reg, word); } +/* + * _pmbus_write_byte_data() is similar to pmbus_write_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->write_byte_data) { + status = info->write_byte_data(client, page, reg, value); + if (status != -ENODATA) + return status; + } + return pmbus_write_byte_data(client, page, reg, value); +} + int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command) { @@ -290,7 +308,7 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id, to = (from & ~mask) | (config & mask); if (to != from) { - rv = pmbus_write_byte_data(client, page, + rv = _pmbus_write_byte_data(client, page, pmbus_fan_config_registers[id], to); if (rv < 0) return rv; @@ -397,7 +415,7 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, tmp = (rv & ~mask) | (value & mask); if (tmp != rv) - rv = pmbus_write_byte_data(client, page, reg, tmp); + rv = _pmbus_write_byte_data(client, page, reg, tmp); return rv; } @@ -912,7 +930,7 @@ static int pmbus_get_boolean(struct i2c_client *client, struct pmbus_boolean *b, regval = status & mask; if (regval) { - ret = pmbus_write_byte_data(client, page, reg, regval); + ret = _pmbus_write_byte_data(client, page, reg, regval); if (ret) goto unlock; } -- cgit v1.2.3 From f0a5c839766334b09e0f280fa10ff5555f71f825 Mon Sep 17 00:00:00 2001 From: Mårten Lindahl Date: Thu, 28 Apr 2022 16:40:37 +0200 Subject: hwmon: (pmbus) Use _pmbus_read_byte_data with callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some of the pmbus core functions uses pmbus_read_byte_data, which does not support driver callbacks for chip specific write operations. This could potentially influence some specific regulator chips that for example need a time delay before each data access. Lets use _pmbus_read_byte_data with callback check. Signed-off-by: Mårten Lindahl Link: https://lore.kernel.org/r/20220428144039.2464667-3-marten.lindahl@axis.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 46 ++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 23 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index e7235d526e99..d2913c8a3896 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -294,6 +294,24 @@ static int _pmbus_write_byte_data(struct i2c_client *client, int page, int reg, return pmbus_write_byte_data(client, page, reg, value); } +/* + * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if + * a device specific mapping function exists and calls it if necessary. + */ +static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->read_byte_data) { + status = info->read_byte_data(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + int pmbus_update_fan(struct i2c_client *client, int page, int id, u8 config, u8 mask, u16 command) { @@ -301,7 +319,7 @@ int pmbus_update_fan(struct i2c_client *client, int page, int id, int rv; u8 to; - from = pmbus_read_byte_data(client, page, + from = _pmbus_read_byte_data(client, page, pmbus_fan_config_registers[id]); if (from < 0) return from; @@ -408,7 +426,7 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, unsigned int tmp; int rv; - rv = pmbus_read_byte_data(client, page, reg); + rv = _pmbus_read_byte_data(client, page, reg); if (rv < 0) return rv; @@ -421,24 +439,6 @@ int pmbus_update_byte_data(struct i2c_client *client, int page, u8 reg, } EXPORT_SYMBOL_NS_GPL(pmbus_update_byte_data, PMBUS); -/* - * _pmbus_read_byte_data() is similar to pmbus_read_byte_data(), but checks if - * a device specific mapping function exists and calls it if necessary. - */ -static int _pmbus_read_byte_data(struct i2c_client *client, int page, int reg) -{ - struct pmbus_data *data = i2c_get_clientdata(client); - const struct pmbus_driver_info *info = data->info; - int status; - - if (info->read_byte_data) { - status = info->read_byte_data(client, page, reg); - if (status != -ENODATA) - return status; - } - return pmbus_read_byte_data(client, page, reg); -} - static struct pmbus_sensor *pmbus_find_sensor(struct pmbus_data *data, int page, int reg) { @@ -473,7 +473,7 @@ static int pmbus_get_fan_rate(struct i2c_client *client, int page, int id, return s->data; } - config = pmbus_read_byte_data(client, page, + config = _pmbus_read_byte_data(client, page, pmbus_fan_config_registers[id]); if (config < 0) return config; @@ -2417,7 +2417,7 @@ static int pmbus_regulator_is_enabled(struct regulator_dev *rdev) int ret; mutex_lock(&data->update_lock); - ret = pmbus_read_byte_data(client, page, PMBUS_OPERATION); + ret = _pmbus_read_byte_data(client, page, PMBUS_OPERATION); mutex_unlock(&data->update_lock); if (ret < 0) @@ -2516,7 +2516,7 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned if (!(func & cat->func)) continue; - status = pmbus_read_byte_data(client, page, cat->reg); + status = _pmbus_read_byte_data(client, page, cat->reg); if (status < 0) { mutex_unlock(&data->update_lock); return status; -- cgit v1.2.3 From b90f994a37cc1fc028807007fbf5f1df7d6bf141 Mon Sep 17 00:00:00 2001 From: Mårten Lindahl Date: Thu, 28 Apr 2022 16:40:38 +0200 Subject: hwmon: (pmbus/ltc2978) Add chip specific write_byte_data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Several of the manuals for devices supported by this driver describes the need for a minimum wait time before the chip is ready to receive next command. This wait time is already implemented in the driver as a ltc_wait_ready function with a driver defined wait time of 100 ms, and is considered for specific devices before reading/writing data on the pmbus. Since this driver uses the default pmbus_regulator_ops for the enable/ disable/is_enabled functions we should add a driver specific callback for write_byte_data to prevent bypassing the wait time recommendations for the following devices: ltc3880/ltc3882/ltc3883/ltc3884/ltc3886/ ltc3887/ltc3889/ltm4664/ltm4675/ltm4676/ltm4677/ltm4678/ltm4680/ltm4686/ ltm4700/ltc7880. Signed-off-by: Mårten Lindahl Link: https://lore.kernel.org/r/20220428144039.2464667-4-marten.lindahl@axis.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/ltc2978.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/ltc2978.c b/drivers/hwmon/pmbus/ltc2978.c index 0127273883f0..531aa674a928 100644 --- a/drivers/hwmon/pmbus/ltc2978.c +++ b/drivers/hwmon/pmbus/ltc2978.c @@ -196,6 +196,17 @@ static int ltc_read_byte_data(struct i2c_client *client, int page, int reg) return pmbus_read_byte_data(client, page, reg); } +static int ltc_write_byte_data(struct i2c_client *client, int page, int reg, u8 value) +{ + int ret; + + ret = ltc_wait_ready(client); + if (ret < 0) + return ret; + + return pmbus_write_byte_data(client, page, reg, value); +} + static int ltc_write_byte(struct i2c_client *client, int page, u8 byte) { int ret; @@ -681,6 +692,7 @@ static int ltc2978_probe(struct i2c_client *client) info = &data->info; info->write_word_data = ltc2978_write_word_data; info->write_byte = ltc_write_byte; + info->write_byte_data = ltc_write_byte_data; info->read_word_data = ltc_read_word_data; info->read_byte_data = ltc_read_byte_data; -- cgit v1.2.3 From 9054416afcb443933c16f9e8c4531086e62eb689 Mon Sep 17 00:00:00 2001 From: "Greg.Schwendimann@infineon.com" Date: Wed, 27 Apr 2022 18:40:12 +0000 Subject: hwmon: (pmbus) Add support for Infineon Digital Multi-phase xdp152 family controllers Add support for devices XDPE152C4, XDPE12584. Signed-off-by: Greg Schwendimann Link: https://lore.kernel.org/r/5e6d50e9b28140158f339b0de343eea4@infineon.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/xdpe152c4.rst | 118 ++++++++++++++++++++++++++++++++++++++ drivers/hwmon/pmbus/Kconfig | 9 +++ drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/xdpe152c4.c | 75 ++++++++++++++++++++++++ 5 files changed, 204 insertions(+) create mode 100644 Documentation/hwmon/xdpe152c4.rst create mode 100644 drivers/hwmon/pmbus/xdpe152c4.c (limited to 'drivers') diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 863b76289159..355c682f9ec4 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -223,6 +223,7 @@ Hardware Monitoring Kernel Drivers wm8350 xgene-hwmon xdpe12284 + xdpe152c4 zl6100 .. only:: subproject and html diff --git a/Documentation/hwmon/xdpe152c4.rst b/Documentation/hwmon/xdpe152c4.rst new file mode 100644 index 000000000000..ab92c32d4d69 --- /dev/null +++ b/Documentation/hwmon/xdpe152c4.rst @@ -0,0 +1,118 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver xdpe152 +===================== + +Supported chips: + + * Infineon XDPE152C4 + + Prefix: 'xdpe152c4' + + * Infineon XDPE15284 + + Prefix: 'xdpe15284' + +Authors: + + Greg Schwendimann + +Description +----------- + +This driver implements support for Infineon Digital Multi-phase Controller +XDPE152C4 and XDPE15284 dual loop voltage regulators. +The devices are compliant with: + +- Intel VR13, VR13HC and VR14 rev 1.86 + converter specification. +- Intel SVID rev 1.93. protocol. +- PMBus rev 1.3.1 interface. + +Devices support linear format for reading input and output voltage, input +and output current, input and output power and temperature. + +Devices support two pages for telemetry. + +The driver provides for current: input, maximum and critical thresholds +and maximum and critical alarms. Low Critical thresholds and Low critical alarm are +supported only for current output. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "iin" and 3, 4 for "iout": + +**curr[1-4]_crit** + +**curr[1-4]_crit_alarm** + +**curr[1-4]_input** + +**curr[1-4]_label** + +**curr[1-4]_max** + +**curr[1-4]_max_alarm** + +**curr[3-4]_lcrit** + +**curr[3-4]_lcrit_alarm** + +**curr[3-4]_rated_max** + +The driver provides for voltage: input, critical and low critical thresholds +and critical and low critical alarms. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "vin" and 3, 4 for "vout": + +**in[1-4]_min** + +**in[1-4]_crit** + +**in[1-4_crit_alarm** + +**in[1-4]_input** + +**in[1-4]_label** + +**in[1-4]_max** + +**in[1-4]_max_alarm** + +**in[1-4]_min** + +**in[1-4]_min_alarm** + +**in[3-4]_lcrit** + +**in[3-4]_lcrit_alarm** + +**in[3-4]_rated_max** + +**in[3-4]_rated_min** + +The driver provides for power: input and alarms. +The driver exports the following attributes for via the sysfs files, where +indexes 1, 2 are for "pin" and 3, 4 for "pout": + +**power[1-2]_alarm** + +**power[1-4]_input** + +**power[1-4]_label** + +**power[1-4]_max** + +**power[1-4]_rated_max** + +The driver provides for temperature: input, maximum and critical thresholds +and maximum and critical alarms. +The driver exports the following attributes for via the sysfs files: + +**temp[1-2]_crit** + +**temp[1-2]_crit_alarm** + +**temp[1-2]_input** + +**temp[1-2]_max** + +**temp[1-2]_max_alarm** diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 53683d2d0fb2..dfae76db65ae 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -408,6 +408,15 @@ config SENSORS_UCD9200 This driver can also be built as a module. If so, the module will be called ucd9200. +config SENSORS_XDPE152 + tristate "Infineon XDPE152 family" + help + If you say yes here you get hardware monitoring support for Infineon + XDPE15284, XDPE152C4, device. + + This driver can also be built as a module. If so, the module will + be called xdpe152c4. + config SENSORS_XDPE122 tristate "Infineon XDPE122 family" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index a4a96ac71de7..4678fba5012c 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -43,5 +43,6 @@ obj-$(CONFIG_SENSORS_TPS53679) += tps53679.o obj-$(CONFIG_SENSORS_UCD9000) += ucd9000.o obj-$(CONFIG_SENSORS_UCD9200) += ucd9200.o obj-$(CONFIG_SENSORS_XDPE122) += xdpe12284.o +obj-$(CONFIG_SENSORS_XDPE152) += xdpe152c4.o obj-$(CONFIG_SENSORS_ZL6100) += zl6100.o obj-$(CONFIG_SENSORS_PIM4328) += pim4328.o diff --git a/drivers/hwmon/pmbus/xdpe152c4.c b/drivers/hwmon/pmbus/xdpe152c4.c new file mode 100644 index 000000000000..b8a36ef73e45 --- /dev/null +++ b/drivers/hwmon/pmbus/xdpe152c4.c @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for Infineon Multi-phase Digital VR Controllers + * + * Copyright (c) 2022 Infineon Technologies. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include "pmbus.h" + +#define XDPE152_PAGE_NUM 2 + +static struct pmbus_driver_info xdpe152_info = { + .pages = XDPE152_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = linear, + .format[PSC_VOLTAGE_OUT] = linear, + .format[PSC_TEMPERATURE] = linear, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = linear, + .format[PSC_POWER] = linear, + .func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_TEMP2 | PMBUS_HAVE_STATUS_TEMP | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, + .func[1] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_POUT | PMBUS_HAVE_PIN | PMBUS_HAVE_STATUS_INPUT, +}; + +static int xdpe152_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + + info = devm_kmemdup(&client->dev, &xdpe152_info, sizeof(*info), + GFP_KERNEL); + if (!info) + return -ENOMEM; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id xdpe152_id[] = { + {"xdpe152c4", 0}, + {"xdpe15284", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, xdpe152_id); + +static const struct of_device_id __maybe_unused xdpe152_of_match[] = { + {.compatible = "infineon,xdpe152c4"}, + {.compatible = "infineon,xdpe15284"}, + {} +}; +MODULE_DEVICE_TABLE(of, xdpe152_of_match); + +static struct i2c_driver xdpe152_driver = { + .driver = { + .name = "xdpe152c4", + .of_match_table = of_match_ptr(xdpe152_of_match), + }, + .probe_new = xdpe152_probe, + .id_table = xdpe152_id, +}; + +module_i2c_driver(xdpe152_driver); + +MODULE_AUTHOR("Greg Schwendimann "); +MODULE_DESCRIPTION("PMBus driver for Infineon XDPE152 family"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(PMBUS); -- cgit v1.2.3 From e0daf1a60ed47828cc3563c5d036692cc7a702e9 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 1 Apr 2022 23:40:30 +0200 Subject: hwmon: (bt1-pvt) use generic polynomial functions The polynomial calculation function was moved into lib/ to be able to reuse it. Move over to this one. Signed-off-by: Michael Walle Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220401214032.3738095-3-michael@walle.cc Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 1 + drivers/hwmon/bt1-pvt.c | 50 +++++++++++++------------------------------------ 2 files changed, 14 insertions(+), 37 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 01a73a0f378a..6d11626c0a09 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -418,6 +418,7 @@ config SENSORS_ATXP1 config SENSORS_BT1_PVT tristate "Baikal-T1 Process, Voltage, Temperature sensor driver" depends on MIPS_BAIKAL_T1 || COMPILE_TEST + select POLYNOMIAL help If you say yes here you get support for Baikal-T1 PVT sensor embedded into the SoC. diff --git a/drivers/hwmon/bt1-pvt.c b/drivers/hwmon/bt1-pvt.c index 74ce5211eb75..21ab172774ec 100644 --- a/drivers/hwmon/bt1-pvt.c +++ b/drivers/hwmon/bt1-pvt.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -65,7 +66,7 @@ static const struct pvt_sensor_info pvt_info[] = { * 48380, * where T = [-48380, 147438] mC and N = [0, 1023]. */ -static const struct pvt_poly __maybe_unused poly_temp_to_N = { +static const struct polynomial __maybe_unused poly_temp_to_N = { .total_divider = 10000, .terms = { {4, 18322, 10000, 10000}, @@ -76,7 +77,7 @@ static const struct pvt_poly __maybe_unused poly_temp_to_N = { } }; -static const struct pvt_poly poly_N_to_temp = { +static const struct polynomial poly_N_to_temp = { .total_divider = 1, .terms = { {4, -16743, 1000, 1}, @@ -97,7 +98,7 @@ static const struct pvt_poly poly_N_to_temp = { * N = (18658e-3*V - 11572) / 10, * V = N * 10^5 / 18658 + 11572 * 10^4 / 18658. */ -static const struct pvt_poly __maybe_unused poly_volt_to_N = { +static const struct polynomial __maybe_unused poly_volt_to_N = { .total_divider = 10, .terms = { {1, 18658, 1000, 1}, @@ -105,7 +106,7 @@ static const struct pvt_poly __maybe_unused poly_volt_to_N = { } }; -static const struct pvt_poly poly_N_to_volt = { +static const struct polynomial poly_N_to_volt = { .total_divider = 10, .terms = { {1, 100000, 18658, 1}, @@ -113,31 +114,6 @@ static const struct pvt_poly poly_N_to_volt = { } }; -/* - * Here is the polynomial calculation function, which performs the - * redistributed terms calculations. It's pretty straightforward. We walk - * over each degree term up to the free one, and perform the redistributed - * multiplication of the term coefficient, its divider (as for the rationale - * fraction representation), data power and the rational fraction divider - * leftover. Then all of this is collected in a total sum variable, which - * value is normalized by the total divider before being returned. - */ -static long pvt_calc_poly(const struct pvt_poly *poly, long data) -{ - const struct pvt_poly_term *term = poly->terms; - long tmp, ret = 0; - int deg; - - do { - tmp = term->coef; - for (deg = 0; deg < term->deg; ++deg) - tmp = mult_frac(tmp, data, term->divider); - ret += tmp / term->divider_leftover; - } while ((term++)->deg); - - return ret / poly->total_divider; -} - static inline u32 pvt_update(void __iomem *reg, u32 mask, u32 data) { u32 old; @@ -324,9 +300,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, } while (read_seqretry(&cache->data_seqlock, seq)); if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } @@ -345,9 +321,9 @@ static int pvt_read_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, data = FIELD_GET(PVT_THRES_HI_MASK, data); if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } @@ -360,10 +336,10 @@ static int pvt_write_limit(struct pvt_hwmon *pvt, enum pvt_sensor_type type, if (type == PVT_TEMP) { val = clamp(val, PVT_TEMP_MIN, PVT_TEMP_MAX); - data = pvt_calc_poly(&poly_temp_to_N, val); + data = polynomial_calc(&poly_temp_to_N, val); } else { val = clamp(val, PVT_VOLT_MIN, PVT_VOLT_MAX); - data = pvt_calc_poly(&poly_volt_to_N, val); + data = polynomial_calc(&poly_volt_to_N, val); } /* Serialize limit update, since a part of the register is changed. */ @@ -522,9 +498,9 @@ static int pvt_read_data(struct pvt_hwmon *pvt, enum pvt_sensor_type type, return -ETIMEDOUT; if (type == PVT_TEMP) - *val = pvt_calc_poly(&poly_N_to_temp, data); + *val = polynomial_calc(&poly_N_to_temp, data); else - *val = pvt_calc_poly(&poly_N_to_volt, data); + *val = polynomial_calc(&poly_N_to_volt, data); return 0; } -- cgit v1.2.3 From c8f55be4a1c7b4f64ecac1369e55f366b7f03bd2 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 1 Apr 2022 23:40:32 +0200 Subject: hwmon: add driver for the Microchip LAN966x SoC Add support for the temperatur sensor and the fan controller on the Microchip LAN966x SoC. Apparently, an Analog Bits PVT sensor is used which can measure temperature and process voltages. But only a forumlae for the temperature sensor is known. Additionally, the SoC support a fan tacho input as well as a PWM signal to control the fan. Signed-off-by: Michael Walle Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220401214032.3738095-5-michael@walle.cc [groeck: Added missing reference in Documentation/hwmon/index.rst] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/lan966x.rst | 40 ++++ drivers/hwmon/Kconfig | 12 ++ drivers/hwmon/Makefile | 1 + drivers/hwmon/lan966x-hwmon.c | 418 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 472 insertions(+) create mode 100644 Documentation/hwmon/lan966x.rst create mode 100644 drivers/hwmon/lan966x-hwmon.c (limited to 'drivers') diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 355c682f9ec4..a72c16872ec2 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -90,6 +90,7 @@ Hardware Monitoring Kernel Drivers jc42 k10temp k8temp + lan966x lineage-pem lm25066 lm63 diff --git a/Documentation/hwmon/lan966x.rst b/Documentation/hwmon/lan966x.rst new file mode 100644 index 000000000000..1d1724afa5d2 --- /dev/null +++ b/Documentation/hwmon/lan966x.rst @@ -0,0 +1,40 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver lan966x-hwmon +=========================== + +Supported chips: + + * Microchip LAN9668 (sensor in SoC) + + Prefix: 'lan9668-hwmon' + + Datasheet: https://microchip-ung.github.io/lan9668_reginfo + +Authors: + + Michael Walle + +Description +----------- + +This driver implements support for the Microchip LAN9668 on-chip +temperature sensor as well as its fan controller. It provides one +temperature sensor and one fan controller. The temperature range +of the sensor is specified from -40 to +125 degrees Celsius and +its accuracy is +/- 5 degrees Celsius. The fan controller has a +tacho input and a PWM output with a customizable PWM output +frequency ranging from ~20Hz to ~650kHz. + +No alarms are supported by the SoC. + +The driver exports temperature values, fan tacho input and PWM +settings via the following sysfs files: + +**temp1_input** + +**fan1_input** + +**pwm1** + +**pwm1_freq** diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6d11626c0a09..684b82597a5d 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -819,6 +819,18 @@ config SENSORS_POWR1220 This driver can also be built as a module. If so, the module will be called powr1220. +config SENSORS_LAN966X + tristate "Microchip LAN966x Hardware Monitoring" + depends on SOC_LAN966 || COMPILE_TEST + select REGMAP + select POLYNOMIAL + help + If you say yes here you get support for temperature monitoring + on the Microchip LAN966x SoC. + + This driver can also be built as a module. If so, the module + will be called lan966x-hwmon. + config SENSORS_LINEAGE tristate "Lineage Compact Power Line Power Entry Module" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 93f2b774cc5e..366fa9e16d08 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -100,6 +100,7 @@ obj-$(CONFIG_SENSORS_IT87) += it87.o obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o +obj-$(CONFIG_SENSORS_LAN966X) += lan966x-hwmon.o obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LOCHNAGAR) += lochnagar-hwmon.o obj-$(CONFIG_SENSORS_LM63) += lm63.o diff --git a/drivers/hwmon/lan966x-hwmon.c b/drivers/hwmon/lan966x-hwmon.c new file mode 100644 index 000000000000..f41df053ac31 --- /dev/null +++ b/drivers/hwmon/lan966x-hwmon.c @@ -0,0 +1,418 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * The original translation formulae of the temperature (in degrees of Celsius) + * are as follows: + * + * T = -3.4627e-11*(N^4) + 1.1023e-7*(N^3) + -1.9165e-4*(N^2) + + * 3.0604e-1*(N^1) + -5.6197e1 + * + * where [-56.197, 136.402]C and N = [0, 1023]. + * + * They must be accordingly altered to be suitable for the integer arithmetics. + * The technique is called 'factor redistribution', which just makes sure the + * multiplications and divisions are made so to have a result of the operations + * within the integer numbers limit. In addition we need to translate the + * formulae to accept millidegrees of Celsius. Here what it looks like after + * the alterations: + * + * T = -34627e-12*(N^4) + 110230e-9*(N^3) + -191650e-6*(N^2) + + * 306040e-3*(N^1) + -56197 + * + * where T = [-56197, 136402]mC and N = [0, 1023]. + */ + +static const struct polynomial poly_N_to_temp = { + .terms = { + {4, -34627, 1000, 1}, + {3, 110230, 1000, 1}, + {2, -191650, 1000, 1}, + {1, 306040, 1000, 1}, + {0, -56197, 1, 1} + } +}; + +#define PVT_SENSOR_CTRL 0x0 /* unused */ +#define PVT_SENSOR_CFG 0x4 +#define SENSOR_CFG_CLK_CFG GENMASK(27, 20) +#define SENSOR_CFG_TRIM_VAL GENMASK(13, 9) +#define SENSOR_CFG_SAMPLE_ENA BIT(8) +#define SENSOR_CFG_START_CAPTURE BIT(7) +#define SENSOR_CFG_CONTINIOUS_MODE BIT(6) +#define SENSOR_CFG_PSAMPLE_ENA GENMASK(1, 0) +#define PVT_SENSOR_STAT 0x8 +#define SENSOR_STAT_DATA_VALID BIT(10) +#define SENSOR_STAT_DATA GENMASK(9, 0) + +#define FAN_CFG 0x0 +#define FAN_CFG_DUTY_CYCLE GENMASK(23, 16) +#define INV_POL BIT(3) +#define GATE_ENA BIT(2) +#define PWM_OPEN_COL_ENA BIT(1) +#define FAN_STAT_CFG BIT(0) +#define FAN_PWM_FREQ 0x4 +#define FAN_PWM_CYC_10US GENMASK(25, 15) +#define FAN_PWM_FREQ_FREQ GENMASK(14, 0) +#define FAN_CNT 0xc +#define FAN_CNT_DATA GENMASK(15, 0) + +#define LAN966X_PVT_CLK 1200000 /* 1.2 MHz */ + +struct lan966x_hwmon { + struct regmap *regmap_pvt; + struct regmap *regmap_fan; + struct clk *clk; + unsigned long clk_rate; +}; + +static int lan966x_hwmon_read_temp(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_pvt, PVT_SENSOR_STAT, &data); + if (ret < 0) + return ret; + + if (!(data & SENSOR_STAT_DATA_VALID)) + return -ENODATA; + + *val = polynomial_calc(&poly_N_to_temp, + FIELD_GET(SENSOR_STAT_DATA, data)); + + return 0; +} + +static int lan966x_hwmon_read_fan(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_CNT, &data); + if (ret < 0) + return ret; + + /* + * Data is given in pulses per second. Assume two pulses + * per revolution. + */ + *val = FIELD_GET(FAN_CNT_DATA, data) * 60 / 2; + + return 0; +} + +static int lan966x_hwmon_read_pwm(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_CFG, &data); + if (ret < 0) + return ret; + + *val = FIELD_GET(FAN_CFG_DUTY_CYCLE, data); + + return 0; +} + +static int lan966x_hwmon_read_pwm_freq(struct device *dev, long *val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + unsigned long tmp; + unsigned int data; + int ret; + + ret = regmap_read(hwmon->regmap_fan, FAN_PWM_FREQ, &data); + if (ret < 0) + return ret; + + /* + * Datasheet says it is sys_clk / 256 / pwm_freq. But in reality + * it is sys_clk / 256 / (pwm_freq + 1). + */ + data = FIELD_GET(FAN_PWM_FREQ_FREQ, data) + 1; + tmp = DIV_ROUND_CLOSEST(hwmon->clk_rate, 256); + *val = DIV_ROUND_CLOSEST(tmp, data); + + return 0; +} + +static int lan966x_hwmon_read(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + switch (type) { + case hwmon_temp: + return lan966x_hwmon_read_temp(dev, val); + case hwmon_fan: + return lan966x_hwmon_read_fan(dev, val); + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return lan966x_hwmon_read_pwm(dev, val); + case hwmon_pwm_freq: + return lan966x_hwmon_read_pwm_freq(dev, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static int lan966x_hwmon_write_pwm(struct device *dev, long val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + + if (val < 0 || val > 255) + return -EINVAL; + + return regmap_update_bits(hwmon->regmap_fan, FAN_CFG, + FAN_CFG_DUTY_CYCLE, + FIELD_PREP(FAN_CFG_DUTY_CYCLE, val)); +} + +static int lan966x_hwmon_write_pwm_freq(struct device *dev, long val) +{ + struct lan966x_hwmon *hwmon = dev_get_drvdata(dev); + + if (val <= 0) + return -EINVAL; + + val = DIV_ROUND_CLOSEST(hwmon->clk_rate, val); + val = DIV_ROUND_CLOSEST(val, 256) - 1; + val = clamp_val(val, 0, FAN_PWM_FREQ_FREQ); + + return regmap_update_bits(hwmon->regmap_fan, FAN_PWM_FREQ, + FAN_PWM_FREQ_FREQ, + FIELD_PREP(FAN_PWM_FREQ_FREQ, val)); +} + +static int lan966x_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + switch (type) { + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + return lan966x_hwmon_write_pwm(dev, val); + case hwmon_pwm_freq: + return lan966x_hwmon_write_pwm_freq(dev, val); + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static umode_t lan966x_hwmon_is_visible(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + umode_t mode = 0; + + switch (type) { + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + mode = 0444; + break; + default: + break; + } + break; + case hwmon_fan: + switch (attr) { + case hwmon_fan_input: + mode = 0444; + break; + default: + break; + } + break; + case hwmon_pwm: + switch (attr) { + case hwmon_pwm_input: + case hwmon_pwm_freq: + mode = 0644; + break; + default: + break; + } + break; + default: + break; + } + + return mode; +} + +static const struct hwmon_channel_info *lan966x_hwmon_info[] = { + HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_FREQ), + NULL +}; + +static const struct hwmon_ops lan966x_hwmon_ops = { + .is_visible = lan966x_hwmon_is_visible, + .read = lan966x_hwmon_read, + .write = lan966x_hwmon_write, +}; + +static const struct hwmon_chip_info lan966x_hwmon_chip_info = { + .ops = &lan966x_hwmon_ops, + .info = lan966x_hwmon_info, +}; + +static void lan966x_hwmon_disable(void *data) +{ + struct lan966x_hwmon *hwmon = data; + + regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, + SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE, + 0); +} + +static int lan966x_hwmon_enable(struct device *dev, + struct lan966x_hwmon *hwmon) +{ + unsigned int mask = SENSOR_CFG_CLK_CFG | + SENSOR_CFG_SAMPLE_ENA | + SENSOR_CFG_START_CAPTURE | + SENSOR_CFG_CONTINIOUS_MODE | + SENSOR_CFG_PSAMPLE_ENA; + unsigned int val; + unsigned int div; + int ret; + + /* enable continuous mode */ + val = SENSOR_CFG_SAMPLE_ENA | SENSOR_CFG_CONTINIOUS_MODE; + + /* set PVT clock to be between 1.15 and 1.25 MHz */ + div = DIV_ROUND_CLOSEST(hwmon->clk_rate, LAN966X_PVT_CLK); + val |= FIELD_PREP(SENSOR_CFG_CLK_CFG, div); + + ret = regmap_update_bits(hwmon->regmap_pvt, PVT_SENSOR_CFG, + mask, val); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, lan966x_hwmon_disable, hwmon); +} + +static struct regmap *lan966x_init_regmap(struct platform_device *pdev, + const char *name) +{ + struct regmap_config regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + }; + void __iomem *base; + + base = devm_platform_ioremap_resource_byname(pdev, name); + if (IS_ERR(base)) + return ERR_CAST(base); + + regmap_config.name = name; + + return devm_regmap_init_mmio(&pdev->dev, base, ®map_config); +} + +static void lan966x_clk_disable(void *data) +{ + struct lan966x_hwmon *hwmon = data; + + clk_disable_unprepare(hwmon->clk); +} + +static int lan966x_clk_enable(struct device *dev, struct lan966x_hwmon *hwmon) +{ + int ret; + + ret = clk_prepare_enable(hwmon->clk); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, lan966x_clk_disable, hwmon); +} + +static int lan966x_hwmon_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lan966x_hwmon *hwmon; + struct device *hwmon_dev; + int ret; + + hwmon = devm_kzalloc(dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->clk = devm_clk_get(dev, NULL); + if (IS_ERR(hwmon->clk)) + return dev_err_probe(dev, PTR_ERR(hwmon->clk), + "failed to get clock\n"); + + ret = lan966x_clk_enable(dev, hwmon); + if (ret) + return dev_err_probe(dev, ret, "failed to enable clock\n"); + + hwmon->clk_rate = clk_get_rate(hwmon->clk); + + hwmon->regmap_pvt = lan966x_init_regmap(pdev, "pvt"); + if (IS_ERR(hwmon->regmap_pvt)) + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_pvt), + "failed to get regmap for PVT registers\n"); + + hwmon->regmap_fan = lan966x_init_regmap(pdev, "fan"); + if (IS_ERR(hwmon->regmap_fan)) + return dev_err_probe(dev, PTR_ERR(hwmon->regmap_fan), + "failed to get regmap for fan registers\n"); + + ret = lan966x_hwmon_enable(dev, hwmon); + if (ret) + return dev_err_probe(dev, ret, "failed to enable sensor\n"); + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "lan966x_hwmon", hwmon, + &lan966x_hwmon_chip_info, NULL); + if (IS_ERR(hwmon_dev)) + return dev_err_probe(dev, PTR_ERR(hwmon_dev), + "failed to register hwmon device\n"); + + return 0; +} + +static const struct of_device_id lan966x_hwmon_of_match[] = { + { .compatible = "microchip,lan9668-hwmon" }, + {} +}; +MODULE_DEVICE_TABLE(of, lan966x_hwmon_of_match); + +static struct platform_driver lan966x_hwmon_driver = { + .probe = lan966x_hwmon_probe, + .driver = { + .name = "lan966x-hwmon", + .of_match_table = lan966x_hwmon_of_match, + }, +}; +module_platform_driver(lan966x_hwmon_driver); + +MODULE_DESCRIPTION("LAN966x Hardware Monitoring Driver"); +MODULE_AUTHOR("Michael Walle "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 7b8664f126e90d1d2129b8653c587bf230dd800c Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 23 Mar 2022 16:40:55 +1300 Subject: hwmon: (adt7475) Add support for pin configuration The adt7473, adt7475, adt7476 and adt7490 have pins that can be used for different functions. On the adt7473 and adt7475 this is pins 5 and 9. On the adt7476 and adt7490 this is pins 10 and 14. The first pin can either be PWM2(default) or SMBALERT#. The second pin can be TACH4(default), THERM#, SMBALERT# or GPIO. The adt7475 driver has always been able to detect the configuration if it had been done by an earlier boot stage. Add support for configuring the pins based on the hardware description in the device tree. Signed-off-by: Chris Packham Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220323034056.260455-3-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7475.c | 96 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c index 9d5b019651f2..6de501de41b2 100644 --- a/drivers/hwmon/adt7475.c +++ b/drivers/hwmon/adt7475.c @@ -112,6 +112,8 @@ #define CONFIG3_THERM 0x02 #define CONFIG4_PINFUNC 0x03 +#define CONFIG4_THERM 0x01 +#define CONFIG4_SMBALERT 0x02 #define CONFIG4_MAXDUTY 0x08 #define CONFIG4_ATTN_IN10 0x30 #define CONFIG4_ATTN_IN43 0xC0 @@ -1460,6 +1462,96 @@ static int adt7475_update_limits(struct i2c_client *client) return 0; } +static int load_config3(const struct i2c_client *client, const char *propname) +{ + const char *function; + u8 config3; + int ret; + + ret = of_property_read_string(client->dev.of_node, propname, &function); + if (!ret) { + ret = adt7475_read(REG_CONFIG3); + if (ret < 0) + return ret; + + config3 = ret & ~CONFIG3_SMBALERT; + if (!strcmp("pwm2", function)) + ; + else if (!strcmp("smbalert#", function)) + config3 |= CONFIG3_SMBALERT; + else + return -EINVAL; + + return i2c_smbus_write_byte_data(client, REG_CONFIG3, config3); + } + + return 0; +} + +static int load_config4(const struct i2c_client *client, const char *propname) +{ + const char *function; + u8 config4; + int ret; + + ret = of_property_read_string(client->dev.of_node, propname, &function); + if (!ret) { + ret = adt7475_read(REG_CONFIG4); + if (ret < 0) + return ret; + + config4 = ret & ~CONFIG4_PINFUNC; + + if (!strcmp("tach4", function)) + ; + else if (!strcmp("therm#", function)) + config4 |= CONFIG4_THERM; + else if (!strcmp("smbalert#", function)) + config4 |= CONFIG4_SMBALERT; + else if (!strcmp("gpio", function)) + config4 |= CONFIG4_PINFUNC; + else + return -EINVAL; + + return i2c_smbus_write_byte_data(client, REG_CONFIG4, config4); + } + + return 0; +} + +static int load_config(const struct i2c_client *client, enum chips chip) +{ + int err; + const char *prop1, *prop2; + + switch (chip) { + case adt7473: + case adt7475: + prop1 = "adi,pin5-function"; + prop2 = "adi,pin9-function"; + break; + case adt7476: + case adt7490: + prop1 = "adi,pin10-function"; + prop2 = "adi,pin14-function"; + break; + } + + err = load_config3(client, prop1); + if (err) { + dev_err(&client->dev, "failed to configure %s\n", prop1); + return err; + } + + err = load_config4(client, prop2); + if (err) { + dev_err(&client->dev, "failed to configure %s\n", prop2); + return err; + } + + return 0; +} + static int set_property_bit(const struct i2c_client *client, char *property, u8 *config, u8 bit_index) { @@ -1585,6 +1677,10 @@ static int adt7475_probe(struct i2c_client *client) revision = adt7475_read(REG_DEVID2) & 0x07; } + ret = load_config(client, chip); + if (ret) + return ret; + config3 = adt7475_read(REG_CONFIG3); /* Pin PWM2 may alternatively be used for ALERT output */ if (!(config3 & CONFIG3_SMBALERT)) -- cgit v1.2.3 From d45cd804280d5cf43d539f49f671ea26552cedc3 Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Wed, 23 Mar 2022 16:40:56 +1300 Subject: hwmon: (adt7475) Use enum chips when loading attenuator settings Make use of enum chips and use a switch statement in load_attenuators() so that the compiler can tell us if we've failed to cater for a supported chip. Signed-off-by: Chris Packham Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220323034056.260455-4-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- drivers/hwmon/adt7475.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/adt7475.c b/drivers/hwmon/adt7475.c index 6de501de41b2..ac480e6e4818 100644 --- a/drivers/hwmon/adt7475.c +++ b/drivers/hwmon/adt7475.c @@ -1569,12 +1569,12 @@ static int set_property_bit(const struct i2c_client *client, char *property, return ret; } -static int load_attenuators(const struct i2c_client *client, int chip, +static int load_attenuators(const struct i2c_client *client, enum chips chip, struct adt7475_data *data) { - int ret; - - if (chip == adt7476 || chip == adt7490) { + switch (chip) { + case adt7476: + case adt7490: set_property_bit(client, "adi,bypass-attenuator-in0", &data->config4, 4); set_property_bit(client, "adi,bypass-attenuator-in1", @@ -1584,18 +1584,15 @@ static int load_attenuators(const struct i2c_client *client, int chip, set_property_bit(client, "adi,bypass-attenuator-in4", &data->config4, 7); - ret = i2c_smbus_write_byte_data(client, REG_CONFIG4, - data->config4); - if (ret < 0) - return ret; - } else if (chip == adt7473 || chip == adt7475) { + return i2c_smbus_write_byte_data(client, REG_CONFIG4, + data->config4); + case adt7473: + case adt7475: set_property_bit(client, "adi,bypass-attenuator-in1", &data->config2, 5); - ret = i2c_smbus_write_byte_data(client, REG_CONFIG2, - data->config2); - if (ret < 0) - return ret; + return i2c_smbus_write_byte_data(client, REG_CONFIG2, + data->config2); } return 0; -- cgit v1.2.3 From 3aa74796cfd038310f68c7e67c804224e5b43809 Mon Sep 17 00:00:00 2001 From: Eduardo Valentin Date: Thu, 28 Apr 2022 10:49:26 -0700 Subject: hwmon: (pmbus) Register with thermal for PSC_TEMPERATURE Some pmbus device drivers have device tree support and may want to use of-thermal to register a thermal zone OF sensor for those device drivers. This way we allow describing device tree thermal zones for pmbus device drivers with device tree support. This patch achieves this by registering pmbus sensors with thermal subsystem if they are PSC_TEMPERATURE and are providing _input hwmon interface. Cc: Guenter Roeck (maintainer:PMBUS HARDWARE MONITORING DRIVERS) Cc: Jean Delvare (maintainer:HARDWARE MONITORING) Cc: linux-hwmon@vger.kernel.org (open list:PMBUS HARDWARE MONITORING DRIVERS) Cc: linux-kernel@vger.kernel.org (open list) Signed-off-by: Eduardo Valentin Signed-off-by: Eduardo Valentin Link: https://lore.kernel.org/r/20220428174926.2150-1-eduval@amazon.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index d2913c8a3896..5a534c2bd037 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "pmbus.h" /* @@ -1101,6 +1103,68 @@ static int pmbus_add_boolean(struct pmbus_data *data, return pmbus_add_attribute(data, &a->dev_attr.attr); } +/* of thermal for pmbus temperature sensors */ +struct pmbus_thermal_data { + struct pmbus_data *pmbus_data; + struct pmbus_sensor *sensor; +}; + +static int pmbus_thermal_get_temp(void *data, int *temp) +{ + struct pmbus_thermal_data *tdata = data; + struct pmbus_sensor *sensor = tdata->sensor; + struct pmbus_data *pmbus_data = tdata->pmbus_data; + struct i2c_client *client = to_i2c_client(pmbus_data->dev); + struct device *dev = pmbus_data->hwmon_dev; + int ret = 0; + + if (!dev) { + /* May not even get to hwmon yet */ + *temp = 0; + return 0; + } + + mutex_lock(&pmbus_data->update_lock); + pmbus_update_sensor_data(client, sensor); + if (sensor->data < 0) + ret = sensor->data; + else + *temp = (int)pmbus_reg2data(pmbus_data, sensor); + mutex_unlock(&pmbus_data->update_lock); + + return ret; +} + +static const struct thermal_zone_of_device_ops pmbus_thermal_ops = { + .get_temp = pmbus_thermal_get_temp, +}; + +static int pmbus_thermal_add_sensor(struct pmbus_data *pmbus_data, + struct pmbus_sensor *sensor, int index) +{ + struct device *dev = pmbus_data->dev; + struct pmbus_thermal_data *tdata; + struct thermal_zone_device *tzd; + + tdata = devm_kzalloc(dev, sizeof(*tdata), GFP_KERNEL); + if (!tdata) + return -ENOMEM; + + tdata->sensor = sensor; + tdata->pmbus_data = pmbus_data; + + tzd = devm_thermal_zone_of_sensor_register(dev, index, tdata, + &pmbus_thermal_ops); + /* + * If CONFIG_THERMAL_OF is disabled, this returns -ENODEV, + * so ignore that error but forward any other error. + */ + if (IS_ERR(tzd) && (PTR_ERR(tzd) != -ENODEV)) + return PTR_ERR(tzd); + + return 0; +} + static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, const char *name, const char *type, int seq, int page, int phase, @@ -1144,6 +1208,10 @@ static struct pmbus_sensor *pmbus_add_sensor(struct pmbus_data *data, sensor->next = data->sensors; data->sensors = sensor; + /* temperature sensors with _input values are registered with thermal */ + if (class == PSC_TEMPERATURE && strcmp(type, "input") == 0) + pmbus_thermal_add_sensor(data, sensor, seq); + return sensor; } -- cgit v1.2.3 From 28bf22ef93eceb42c7583f0909bc9dedc07770e3 Mon Sep 17 00:00:00 2001 From: Mårten Lindahl Date: Tue, 3 May 2022 12:46:31 +0200 Subject: hwmon: (pmbus) Add get_voltage/set_voltage ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pmbus core does not have operations for getting or setting voltage. Add functions get/set voltage for the dynamic regulator framework. Signed-off-by: Mårten Lindahl Link: https://lore.kernel.org/r/20220503104631.3515715-5-marten.lindahl@axis.com [groeck: cosmetic alignment / empty line fixes] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index 5a534c2bd037..cf0e77383898 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2634,11 +2634,78 @@ static int pmbus_regulator_get_error_flags(struct regulator_dev *rdev, unsigned return 0; } +static int pmbus_regulator_get_voltage(struct regulator_dev *rdev) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + }; + + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_READ_VOUT); + if (s.data < 0) + return s.data; + + return (int)pmbus_reg2data(data, &s) * 1000; /* unit is uV */ +} + +static int pmbus_regulator_set_voltage(struct regulator_dev *rdev, int min_uv, + int max_uv, unsigned int *selector) +{ + struct device *dev = rdev_get_dev(rdev); + struct i2c_client *client = to_i2c_client(dev->parent); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor s = { + .page = rdev_get_id(rdev), + .class = PSC_VOLTAGE_OUT, + .convert = true, + .data = -1, + }; + int val = DIV_ROUND_CLOSEST(min_uv, 1000); /* convert to mV */ + int low, high; + + *selector = 0; + + if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MIN)) + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MIN); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_LOW); + if (s.data < 0) + return s.data; + } + low = pmbus_reg2data(data, &s); + + s.data = -1; + if (pmbus_check_word_register(client, s.page, PMBUS_MFR_VOUT_MAX)) + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_MFR_VOUT_MAX); + if (s.data < 0) { + s.data = _pmbus_read_word_data(client, s.page, 0xff, PMBUS_VOUT_MARGIN_HIGH); + if (s.data < 0) + return s.data; + } + high = pmbus_reg2data(data, &s); + + /* Make sure we are within margins */ + if (low > val) + val = low; + if (high < val) + val = high; + + val = pmbus_data2reg(data, &s, val); + + return _pmbus_write_word_data(client, s.page, PMBUS_VOUT_COMMAND, (u16)val); +} + const struct regulator_ops pmbus_regulator_ops = { .enable = pmbus_regulator_enable, .disable = pmbus_regulator_disable, .is_enabled = pmbus_regulator_is_enabled, .get_error_flags = pmbus_regulator_get_error_flags, + .get_voltage = pmbus_regulator_get_voltage, + .set_voltage = pmbus_regulator_set_voltage, }; EXPORT_SYMBOL_NS_GPL(pmbus_regulator_ops, PMBUS); -- cgit v1.2.3 From 512a4da1d9f55d9e3be87b2ea6d0a51ef342e764 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Sun, 8 May 2022 16:46:01 +0200 Subject: hwmon: (lm83) Remove unused include directives Some include directives are no longer necessary due to previous driver changes. Remove them now to further improve driver code clarity. Mutex usage has ceased since commit 719af4f1a40b ("hwmon: (lm83) Use regmap"). Ever since commit a0ac840d99fa ("hwmon: (lm83) Convert to use devm_hwmon_device_register_with_groups") functions sysfs_create_group and sysfs_remove_group are no longer used by the driver. Signed-off-by: Karl Mehltretter Link: https://lore.kernel.org/r/20220508144601.22796-1-kmehltretter@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm83.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/lm83.c b/drivers/hwmon/lm83.c index 12370dcefa6a..905f5689f907 100644 --- a/drivers/hwmon/lm83.c +++ b/drivers/hwmon/lm83.c @@ -24,10 +24,8 @@ #include #include #include -#include #include #include -#include /* * Addresses to scan -- cgit v1.2.3 From 764124082805b41f2f203c37f80657f41f139aaf Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Sat, 7 May 2022 10:29:33 +0300 Subject: hwmon: (nct6775) add ASUS PRO H410T / PRIME H410M-R / ROG X570-E GAMING WIFI II Boards such as * PRO H410T * PRIME H410M-R * ROG STRIX X570-E GAMING WIFI II have got a nct6775 chip, but by default there's no use of it because of resource conflict with WMI method. This commit adds such boards to the WMI monitoring list. BugLink: https://bugzilla.kernel.org/show_bug.cgi?id=204807 Signed-off-by: Denis Pauk Reported-by: renedis Reported-by: Dmitrii Levchenko Reported-by: Hubert Banas Link: https://lore.kernel.org/r/20220507072933.3013-1-pauk.denis@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-platform.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index c2f76af735a2..6d46c9401898 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -1042,6 +1042,7 @@ static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) static struct platform_device *pdev[2]; static const char * const asus_wmi_boards[] = { + "PRO H410T", "ProArt X570-CREATOR WIFI", "Pro B550M-C", "Pro WS X570-ACE", @@ -1050,6 +1051,7 @@ static const char * const asus_wmi_boards[] = { "PRIME B550-PLUS", "PRIME B550M-A", "PRIME B550M-A (WI-FI)", + "PRIME H410M-R", "PRIME X570-P", "PRIME X570-PRO", "ROG CROSSHAIR VIII DARK HERO", @@ -1064,6 +1066,7 @@ static const char * const asus_wmi_boards[] = { "ROG STRIX B550-I GAMING", "ROG STRIX B550-XE GAMING (WI-FI)", "ROG STRIX X570-E GAMING", + "ROG STRIX X570-E GAMING WIFI II", "ROG STRIX X570-F GAMING", "ROG STRIX X570-I GAMING", "ROG STRIX Z390-E GAMING", -- cgit v1.2.3 From 6bb77c55b05fccea588fdc069008fa81e7359679 Mon Sep 17 00:00:00 2001 From: Zev Weiss Date: Wed, 27 Apr 2022 18:27:07 -0700 Subject: hwmon: (nct6775) Add i2c driver This driver provides an i2c I/O mechanism for the core nct6775 driver, as might be used by a BMC. Because the Super I/O chip is shared with the host CPU in such a scenario (and the host should ultimately be in control of it), the i2c driver is strictly read-only to avoid interfering with any usage by the host (aside from the bank-select register, which seems to be replicated for the i2c interface). Signed-off-by: Zev Weiss Tested-by: Renze Nicolai Link: https://lore.kernel.org/r/20220428012707.24921-3-zev@bewilderbeest.net Signed-off-by: Guenter Roeck --- MAINTAINERS | 7 ++ drivers/hwmon/Kconfig | 17 ++++ drivers/hwmon/Makefile | 1 + drivers/hwmon/nct6775-i2c.c | 195 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 220 insertions(+) create mode 100644 drivers/hwmon/nct6775-i2c.c (limited to 'drivers') diff --git a/MAINTAINERS b/MAINTAINERS index 6ee5f2cf4ad2..b3ffdd103e53 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -13546,6 +13546,13 @@ F: drivers/hwmon/nct6775-core.c F: drivers/hwmon/nct6775-platform.c F: drivers/hwmon/nct6775.h +NCT6775 HARDWARE MONITOR DRIVER - I2C DRIVER +M: Zev Weiss +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/hwmon/nuvoton,nct6775.yaml +F: drivers/hwmon/nct6775-i2c.c + NETDEVSIM M: Jakub Kicinski S: Maintained diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 684b82597a5d..590d3d550acb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1502,6 +1502,23 @@ config SENSORS_NCT6775 This driver can also be built as a module. If so, the module will be called nct6775. +config SENSORS_NCT6775_I2C + tristate "I2C driver for Nuvoton NCT6775F and compatibles" + depends on I2C + select REGMAP_I2C + select SENSORS_NCT6775_CORE + help + If you say yes here you get support for the hardware monitoring + functionality of the Nuvoton NCT6106D, NCT6775F, NCT6776F, NCT6779D, + NCT6791D, NCT6792D, NCT6793D, NCT6795D, NCT6796D, and compatible + Super-I/O chips via their I2C interface. + + If you're not building a kernel for a BMC, this is probably + not the driver you want (see CONFIG_SENSORS_NCT6775). + + This driver can also be built as a module. If so, the module + will be called nct6775-i2c. + config SENSORS_NCT7802 tristate "Nuvoton NCT7802Y" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 366fa9e16d08..007e829d1d0d 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -158,6 +158,7 @@ obj-$(CONFIG_SENSORS_NCT6683) += nct6683.o obj-$(CONFIG_SENSORS_NCT6775_CORE) += nct6775-core.o nct6775-objs := nct6775-platform.o obj-$(CONFIG_SENSORS_NCT6775) += nct6775.o +obj-$(CONFIG_SENSORS_NCT6775_I2C) += nct6775-i2c.o obj-$(CONFIG_SENSORS_NCT7802) += nct7802.o obj-$(CONFIG_SENSORS_NCT7904) += nct7904.o obj-$(CONFIG_SENSORS_NPCM7XX) += npcm750-pwm-fan.o diff --git a/drivers/hwmon/nct6775-i2c.c b/drivers/hwmon/nct6775-i2c.c new file mode 100644 index 000000000000..e1bcd1146191 --- /dev/null +++ b/drivers/hwmon/nct6775-i2c.c @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * nct6775-i2c - I2C driver for the hardware monitoring functionality of + * Nuvoton NCT677x Super-I/O chips + * + * Copyright (C) 2022 Zev Weiss + * + * This driver interacts with the chip via it's "back door" i2c interface, as + * is often exposed to a BMC. Because the host may still be operating the + * chip via the ("front door") LPC interface, this driver cannot assume that + * it actually has full control of the chip, and in particular must avoid + * making any changes that could confuse the host's LPC usage of it. It thus + * operates in a strictly read-only fashion, with the only exception being the + * bank-select register (which seems, thankfully, to be replicated for the i2c + * interface so it doesn't affect the LPC interface). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "nct6775.h" + +static int nct6775_i2c_read(void *ctx, unsigned int reg, unsigned int *val) +{ + int ret; + u32 tmp; + u8 bank = reg >> 8; + struct nct6775_data *data = ctx; + struct i2c_client *client = data->driver_data; + + if (bank != data->bank) { + ret = i2c_smbus_write_byte_data(client, NCT6775_REG_BANK, bank); + if (ret) + return ret; + data->bank = bank; + } + + ret = i2c_smbus_read_byte_data(client, reg & 0xff); + if (ret < 0) + return ret; + tmp = ret; + + if (nct6775_reg_is_word_sized(data, reg)) { + ret = i2c_smbus_read_byte_data(client, (reg & 0xff) + 1); + if (ret < 0) + return ret; + tmp = (tmp << 8) | ret; + } + + *val = tmp; + return 0; +} + +/* + * The write operation is a dummy so as not to disturb anything being done + * with the chip via LPC. + */ +static int nct6775_i2c_write(void *ctx, unsigned int reg, unsigned int value) +{ + struct nct6775_data *data = ctx; + struct i2c_client *client = data->driver_data; + + dev_dbg(&client->dev, "skipping attempted write: %02x -> %03x\n", value, reg); + + /* + * This is a lie, but writing anything but the bank-select register is + * something this driver shouldn't be doing. + */ + return 0; +} + +static const struct of_device_id __maybe_unused nct6775_i2c_of_match[] = { + { .compatible = "nuvoton,nct6106", .data = (void *)nct6106, }, + { .compatible = "nuvoton,nct6116", .data = (void *)nct6116, }, + { .compatible = "nuvoton,nct6775", .data = (void *)nct6775, }, + { .compatible = "nuvoton,nct6776", .data = (void *)nct6776, }, + { .compatible = "nuvoton,nct6779", .data = (void *)nct6779, }, + { .compatible = "nuvoton,nct6791", .data = (void *)nct6791, }, + { .compatible = "nuvoton,nct6792", .data = (void *)nct6792, }, + { .compatible = "nuvoton,nct6793", .data = (void *)nct6793, }, + { .compatible = "nuvoton,nct6795", .data = (void *)nct6795, }, + { .compatible = "nuvoton,nct6796", .data = (void *)nct6796, }, + { .compatible = "nuvoton,nct6797", .data = (void *)nct6797, }, + { .compatible = "nuvoton,nct6798", .data = (void *)nct6798, }, + { }, +}; +MODULE_DEVICE_TABLE(of, nct6775_i2c_of_match); + +static const struct i2c_device_id nct6775_i2c_id[] = { + { "nct6106", nct6106 }, + { "nct6116", nct6116 }, + { "nct6775", nct6775 }, + { "nct6776", nct6776 }, + { "nct6779", nct6779 }, + { "nct6791", nct6791 }, + { "nct6792", nct6792 }, + { "nct6793", nct6793 }, + { "nct6795", nct6795 }, + { "nct6796", nct6796 }, + { "nct6797", nct6797 }, + { "nct6798", nct6798 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, nct6775_i2c_id); + +static int nct6775_i2c_probe_init(struct nct6775_data *data) +{ + u32 tsi_channel_mask; + struct i2c_client *client = data->driver_data; + + /* + * The i2c interface doesn't provide access to the control registers + * needed to determine the presence of other fans, but fans 1 and 2 + * are (in principle) always there. + * + * In practice this is perhaps a little silly, because the system + * using this driver is mostly likely a BMC, and hence probably has + * totally separate fan tachs & pwms of its own that are actually + * controlling/monitoring the fans -- these are thus unlikely to be + * doing anything actually useful. + */ + data->has_fan = 0x03; + data->has_fan_min = 0x03; + data->has_pwm = 0x03; + + /* + * Because on a BMC this driver may be bound very shortly after power + * is first applied to the device, the automatic TSI channel detection + * in nct6775_probe() (which has already been run at this point) may + * not find anything if a channel hasn't yet produced a temperature + * reading. Augment whatever was found via autodetection (if + * anything) with the channels DT says should be active. + */ + if (!of_property_read_u32(client->dev.of_node, "nuvoton,tsi-channel-mask", + &tsi_channel_mask)) + data->have_tsi_temp |= tsi_channel_mask & GENMASK(NUM_TSI_TEMP - 1, 0); + + return 0; +} + +static const struct regmap_config nct6775_i2c_regmap_config = { + .reg_bits = 16, + .val_bits = 16, + .reg_read = nct6775_i2c_read, + .reg_write = nct6775_i2c_write, +}; + +static int nct6775_i2c_probe(struct i2c_client *client) +{ + struct nct6775_data *data; + const struct of_device_id *of_id; + const struct i2c_device_id *i2c_id; + struct device *dev = &client->dev; + + of_id = of_match_device(nct6775_i2c_of_match, dev); + i2c_id = i2c_match_id(nct6775_i2c_id, client); + + if (of_id && (unsigned long)of_id->data != i2c_id->driver_data) + dev_notice(dev, "Device mismatch: %s in device tree, %s detected\n", + of_id->name, i2c_id->name); + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->kind = i2c_id->driver_data; + + data->read_only = true; + data->driver_data = client; + data->driver_init = nct6775_i2c_probe_init; + + return nct6775_probe(dev, data, &nct6775_i2c_regmap_config); +} + +static struct i2c_driver nct6775_i2c_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "nct6775-i2c", + .of_match_table = of_match_ptr(nct6775_i2c_of_match), + }, + .probe_new = nct6775_i2c_probe, + .id_table = nct6775_i2c_id, +}; + +module_i2c_driver(nct6775_i2c_driver); + +MODULE_AUTHOR("Zev Weiss "); +MODULE_DESCRIPTION("I2C driver for NCT6775F and compatible chips"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS(HWMON_NCT6775); -- cgit v1.2.3 From 9ccafe466c3242a17ae7d2ea1b02986bb94fd587 Mon Sep 17 00:00:00 2001 From: Debabrata Banerjee Date: Thu, 5 May 2022 09:33:51 +0200 Subject: hwmon: (asus-ec-sensors) add ROG STRIX X570-E GAMING WIFI II Adds support for the ROG STRIX X570-E GAMING WIFI II board and simplifies formatting for the list of supported models. Signed-off-by: Debabrata Banerjee Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220505073351.123753-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 23 ++++++++++++----------- drivers/hwmon/asus-ec-sensors.c | 8 ++++++++ 2 files changed, 20 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 1700fe619597..78ca69eda877 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -4,19 +4,20 @@ Kernel driver asus_ec_sensors ================================= Supported boards: - * PRIME X470-PRO, - * PRIME X570-PRO, - * Pro WS X570-ACE, + * PRIME X470-PRO + * PRIME X570-PRO + * Pro WS X570-ACE * ProArt X570-CREATOR WIFI - * ROG CROSSHAIR VIII DARK HERO, + * ROG CROSSHAIR VIII DARK HERO * ROG CROSSHAIR VIII HERO (WI-FI) - * ROG CROSSHAIR VIII FORMULA, - * ROG CROSSHAIR VIII HERO, - * ROG CROSSHAIR VIII IMPACT, - * ROG STRIX B550-E GAMING, - * ROG STRIX B550-I GAMING, - * ROG STRIX X570-E GAMING, - * ROG STRIX X570-F GAMING, + * ROG CROSSHAIR VIII FORMULA + * ROG CROSSHAIR VIII HERO + * ROG CROSSHAIR VIII IMPACT + * ROG STRIX B550-E GAMING + * ROG STRIX B550-I GAMING + * ROG STRIX X570-E GAMING + * ROG STRIX X570-E GAMING WIFI II + * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING Authors: diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 998d49d6b799..611e897429b1 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -315,6 +315,14 @@ static const struct ec_board_info board_info[] = { .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, .family = family_amd_500_series, }, + { + .board_names = {"ROG STRIX X570-E GAMING WIFI II"}, + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, + }, { .board_names = {"ROG STRIX X570-F GAMING"}, .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | -- cgit v1.2.3 From 59e746ca86ff4f73183f6ff0e6b046705b00e081 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Mon, 9 May 2022 06:30:09 +0000 Subject: hwmon: (acpi_power_meter) Fix style issues Fix style issues found by checkpatch. Signed-off-by: Corentin Labbe Link: https://lore.kernel.org/r/20220509063010.3878134-2-clabbe@baylibre.com Signed-off-by: Guenter Roeck --- drivers/hwmon/acpi_power_meter.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/acpi_power_meter.c b/drivers/hwmon/acpi_power_meter.c index c405a5869581..d2545a1be9fc 100644 --- a/drivers/hwmon/acpi_power_meter.c +++ b/drivers/hwmon/acpi_power_meter.c @@ -481,7 +481,7 @@ static struct sensor_template meter_attrs[] = { RO_SENSOR_TEMPLATE("power1_average_interval_max", show_val, 1), RO_SENSOR_TEMPLATE("power1_is_battery", show_val, 5), RW_SENSOR_TEMPLATE(POWER_AVG_INTERVAL_NAME, show_avg_interval, - set_avg_interval, 0), + set_avg_interval, 0), {}, }; @@ -530,6 +530,7 @@ static void remove_domain_devices(struct acpi_power_meter_resource *resource) for (i = 0; i < resource->num_domain_devices; i++) { struct acpi_device *obj = resource->domain_devices[i]; + if (!obj) continue; @@ -580,7 +581,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) } resource->holders_dir = kobject_create_and_add("measures", - &resource->acpi_dev->dev.kobj); + &resource->acpi_dev->dev.kobj); if (!resource->holders_dir) { res = -ENOMEM; goto exit_free; @@ -590,7 +591,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) for (i = 0; i < pss->package.count; i++) { struct acpi_device *obj; - union acpi_object *element = &(pss->package.elements[i]); + union acpi_object *element = &pss->package.elements[i]; /* Refuse non-references */ if (element->type != ACPI_TYPE_LOCAL_REFERENCE) @@ -603,7 +604,7 @@ static int read_domain_devices(struct acpi_power_meter_resource *resource) continue; res = sysfs_create_link(resource->holders_dir, &obj->dev.kobj, - kobject_name(&obj->dev.kobj)); + kobject_name(&obj->dev.kobj)); if (res) { acpi_dev_put(obj); resource->domain_devices[i] = NULL; @@ -788,7 +789,7 @@ static int read_capabilities(struct acpi_power_meter_resource *resource) str = &resource->model_number; for (i = 11; i < 14; i++) { - union acpi_object *element = &(pss->package.elements[i]); + union acpi_object *element = &pss->package.elements[i]; if (element->type != ACPI_TYPE_STRING) { res = -EINVAL; @@ -868,8 +869,7 @@ static int acpi_power_meter_add(struct acpi_device *device) if (!device) return -EINVAL; - resource = kzalloc(sizeof(struct acpi_power_meter_resource), - GFP_KERNEL); + resource = kzalloc(sizeof(*resource), GFP_KERNEL); if (!resource) return -ENOMEM; @@ -884,7 +884,8 @@ static int acpi_power_meter_add(struct acpi_device *device) if (res) goto exit_free; - resource->trip[0] = resource->trip[1] = -1; + resource->trip[0] = -1; + resource->trip[1] = -1; res = setup_attrs(resource); if (res) -- cgit v1.2.3 From e5d21072054fbadf41cd56062a3a14e447e8c22b Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 11 May 2022 06:29:59 -0700 Subject: hwmon: Introduce hwmon_device_register_for_thermal The thermal subsystem registers a hwmon driver without providing chip or sysfs group information. This is for legacy reasons and would be difficult to change. At the same time, we want to enforce that chip information is provided when registering a hwmon device using hwmon_device_register_with_info(). To enable this, introduce a special API for use only by the thermal subsystem. Acked-by: Rafael J . Wysocki Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 25 +++++++++++++++++++++++++ include/linux/hwmon.h | 3 +++ 2 files changed, 28 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 5915ccfdb7d9..13053a4edc9e 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -916,6 +916,31 @@ hwmon_device_register_with_info(struct device *dev, const char *name, } EXPORT_SYMBOL_GPL(hwmon_device_register_with_info); +/** + * hwmon_device_register_for_thermal - register hwmon device for thermal subsystem + * @dev: the parent device + * @name: hwmon name attribute + * @drvdata: driver data to attach to created device + * + * The use of this function is restricted. It is provided for legacy reasons + * and must only be called from the thermal subsystem. + * + * hwmon_device_unregister() must be called when the device is no + * longer needed. + * + * Returns the pointer to the new device. + */ +struct device * +hwmon_device_register_for_thermal(struct device *dev, const char *name, + void *drvdata) +{ + if (!name || !dev) + return ERR_PTR(-EINVAL); + + return __hwmon_device_register(dev, name, drvdata, NULL, NULL); +} +EXPORT_SYMBOL_NS_GPL(hwmon_device_register_for_thermal, HWMON_THERMAL); + /** * hwmon_device_register - register w/ hwmon * @dev: the device to register diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 4efaf06fd2b8..14325f93c6b2 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -450,6 +450,9 @@ hwmon_device_register_with_info(struct device *dev, const struct hwmon_chip_info *info, const struct attribute_group **extra_groups); struct device * +hwmon_device_register_for_thermal(struct device *dev, const char *name, + void *drvdata); +struct device * devm_hwmon_device_register_with_info(struct device *dev, const char *name, void *drvdata, const struct hwmon_chip_info *info, -- cgit v1.2.3 From 87743bcf08072b3e1952a0bf5524b2833e667b4c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 11 May 2022 06:36:29 -0700 Subject: thermal/drivers/thermal_hwmon: Use hwmon_device_register_for_thermal() The thermal subsystem registers a hwmon device without providing chip information or sysfs attribute groups. While undesirable, it would be difficult to change. On the other side, it abuses the hwmon_device_register_with_info API by not providing that information. Use new API specifically created for the thermal subsystem instead to let us enforce the 'chip' parameter for other callers of hwmon_device_register_with_info(). Acked-by: Rafael J . Wysocki Signed-off-by: Guenter Roeck --- drivers/thermal/thermal_hwmon.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/thermal/thermal_hwmon.c b/drivers/thermal/thermal_hwmon.c index ad03262cca56..09e49ec8b6f4 100644 --- a/drivers/thermal/thermal_hwmon.c +++ b/drivers/thermal/thermal_hwmon.c @@ -149,8 +149,8 @@ int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) INIT_LIST_HEAD(&hwmon->tz_list); strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); strreplace(hwmon->type, '-', '_'); - hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, - hwmon, NULL, NULL); + hwmon->device = hwmon_device_register_for_thermal(&tz->device, + hwmon->type, hwmon); if (IS_ERR(hwmon->device)) { result = PTR_ERR(hwmon->device); goto free_mem; @@ -277,3 +277,5 @@ int devm_thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) return ret; } EXPORT_SYMBOL_GPL(devm_thermal_add_hwmon_sysfs); + +MODULE_IMPORT_NS(HWMON_THERMAL); -- cgit v1.2.3 From ddaefa209c4ac791c1262e97c9b2d0440c8ef1d5 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 11 May 2022 06:22:51 -0700 Subject: hwmon: Make chip parameter for with_info API mandatory Various attempts were made recently to "convert" the old hwmon_device_register() API to devm_hwmon_device_register_with_info() by just changing the function name without actually converting the driver. Prevent this from happening by making the 'chip' parameter of devm_hwmon_device_register_with_info() mandatory. Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 2 +- drivers/hwmon/hwmon.c | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index e2975d5caf34..f3276b3a381a 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -76,7 +76,7 @@ hwmon_device_register_with_info is the most comprehensive and preferred means to register a hardware monitoring device. It creates the standard sysfs attributes in the hardware monitoring core, letting the driver focus on reading from and writing to the chip instead of having to bother with sysfs attributes. -The parent device parameter cannot be NULL with non-NULL chip info. Its +The parent device parameter as well as the chip parameter must not be NULL. Its parameters are described in more detail below. devm_hwmon_device_register_with_info is similar to diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 13053a4edc9e..22de7a9e7ba7 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -886,11 +886,12 @@ EXPORT_SYMBOL_GPL(hwmon_device_register_with_groups); /** * hwmon_device_register_with_info - register w/ hwmon - * @dev: the parent device - * @name: hwmon name attribute - * @drvdata: driver data to attach to created device - * @chip: pointer to hwmon chip information + * @dev: the parent device (mandatory) + * @name: hwmon name attribute (mandatory) + * @drvdata: driver data to attach to created device (optional) + * @chip: pointer to hwmon chip information (mandatory) * @extra_groups: pointer to list of additional non-standard attribute groups + * (optional) * * hwmon_device_unregister() must be called when the device is no * longer needed. @@ -903,13 +904,10 @@ hwmon_device_register_with_info(struct device *dev, const char *name, const struct hwmon_chip_info *chip, const struct attribute_group **extra_groups) { - if (!name) - return ERR_PTR(-EINVAL); - - if (chip && (!chip->ops || !chip->ops->is_visible || !chip->info)) + if (!dev || !name || !chip) return ERR_PTR(-EINVAL); - if (chip && !dev) + if (!chip->ops || !chip->ops->is_visible || !chip->info) return ERR_PTR(-EINVAL); return __hwmon_device_register(dev, name, drvdata, chip, extra_groups); -- cgit v1.2.3 From 6b767ccd3b7ef8a17313ec8d07d5c4c7623d1e67 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:57 -0700 Subject: hwmon: (as370-hwmon) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Signed-off-by: Guenter Roeck --- drivers/hwmon/as370-hwmon.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/as370-hwmon.c b/drivers/hwmon/as370-hwmon.c index 464244ba8d58..63b5b2d6e593 100644 --- a/drivers/hwmon/as370-hwmon.c +++ b/drivers/hwmon/as370-hwmon.c @@ -76,18 +76,8 @@ as370_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, } } -static const u32 as370_hwmon_temp_config[] = { - HWMON_T_INPUT, - 0 -}; - -static const struct hwmon_channel_info as370_hwmon_temp = { - .type = hwmon_temp, - .config = as370_hwmon_temp_config, -}; - static const struct hwmon_channel_info *as370_hwmon_info[] = { - &as370_hwmon_temp, + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), NULL }; -- cgit v1.2.3 From c2e813438ef562eee81a42a7fe8f15fd7d9d29b9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:58 -0700 Subject: hwmon: (ltc2992) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc2992.c | 86 ++++++++++++++----------------------------------- 1 file changed, 25 insertions(+), 61 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/ltc2992.c b/drivers/hwmon/ltc2992.c index 7352d2b3c756..72489d5d7eaf 100644 --- a/drivers/hwmon/ltc2992.c +++ b/drivers/hwmon/ltc2992.c @@ -811,68 +811,32 @@ static const struct hwmon_ops ltc2992_hwmon_ops = { .write = ltc2992_write, }; -static const u32 ltc2992_chip_config[] = { - HWMON_C_IN_RESET_HISTORY, - 0 -}; - -static const struct hwmon_channel_info ltc2992_chip = { - .type = hwmon_chip, - .config = ltc2992_chip_config, -}; - -static const u32 ltc2992_in_config[] = { - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | HWMON_I_MAX | - HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_in = { - .type = hwmon_in, - .config = ltc2992_in_config, -}; - -static const u32 ltc2992_curr_config[] = { - HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | - HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, - HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | HWMON_C_MAX | - HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_curr = { - .type = hwmon_curr, - .config = ltc2992_curr_config, -}; - -static const u32 ltc2992_power_config[] = { - HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | - HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, - HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | HWMON_P_MIN | HWMON_P_MAX | - HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, - 0 -}; - -static const struct hwmon_channel_info ltc2992_power = { - .type = hwmon_power, - .config = ltc2992_power_config, -}; - static const struct hwmon_channel_info *ltc2992_info[] = { - <c2992_chip, - <c2992_in, - <c2992_curr, - <c2992_power, + HWMON_CHANNEL_INFO(chip, + HWMON_C_IN_RESET_HISTORY), + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM, + HWMON_I_INPUT | HWMON_I_LOWEST | HWMON_I_HIGHEST | HWMON_I_MIN | + HWMON_I_MAX | HWMON_I_MIN_ALARM | HWMON_I_MAX_ALARM), + HWMON_CHANNEL_INFO(curr, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | + HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM, + HWMON_C_INPUT | HWMON_C_LOWEST | HWMON_C_HIGHEST | HWMON_C_MIN | + HWMON_C_MAX | HWMON_C_MIN_ALARM | HWMON_C_MAX_ALARM), + HWMON_CHANNEL_INFO(power, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | + HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM, + HWMON_P_INPUT | HWMON_P_INPUT_LOWEST | HWMON_P_INPUT_HIGHEST | + HWMON_P_MIN | HWMON_P_MAX | HWMON_P_MIN_ALARM | HWMON_P_MAX_ALARM), NULL }; -- cgit v1.2.3 From 9070d8618eb2c6b7bfecc20a374f98bda4836026 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:58 -0700 Subject: hwmon: (mr75203) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Cc: Philipp Zabel Signed-off-by: Guenter Roeck --- drivers/hwmon/mr75203.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/mr75203.c b/drivers/hwmon/mr75203.c index 1ba1e3145969..26278b0f17a9 100644 --- a/drivers/hwmon/mr75203.c +++ b/drivers/hwmon/mr75203.c @@ -223,16 +223,6 @@ static int pvt_read(struct device *dev, enum hwmon_sensor_types type, } } -static const u32 pvt_chip_config[] = { - HWMON_C_REGISTER_TZ, - 0 -}; - -static const struct hwmon_channel_info pvt_chip = { - .type = hwmon_chip, - .config = pvt_chip_config, -}; - static struct hwmon_channel_info pvt_temp = { .type = hwmon_temp, }; @@ -555,7 +545,7 @@ static int mr75203_probe(struct platform_device *pdev) pvt_info = devm_kcalloc(dev, val + 2, sizeof(*pvt_info), GFP_KERNEL); if (!pvt_info) return -ENOMEM; - pvt_info[0] = &pvt_chip; + pvt_info[0] = HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ); index = 1; if (ts_num) { -- cgit v1.2.3 From 09e02c8e632a1717e6ffaf352cfeb2840bc861d2 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:59 -0700 Subject: hwmon: (peci/cputemp) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Cc: Iwona Winiarska Reviewed-by: Iwona Winiarska Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/cputemp.c | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/peci/cputemp.c b/drivers/hwmon/peci/cputemp.c index 12156328f5cf..57470fda5f6c 100644 --- a/drivers/hwmon/peci/cputemp.c +++ b/drivers/hwmon/peci/cputemp.c @@ -447,29 +447,23 @@ static const struct hwmon_ops peci_cputemp_ops = { .read = cputemp_read, }; -static const u32 peci_cputemp_temp_channel_config[] = { - /* Die temperature */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST, - /* DTS margin */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT | HWMON_T_CRIT_HYST, - /* Tcontrol temperature */ - HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, - /* Tthrottle temperature */ - HWMON_T_LABEL | HWMON_T_INPUT, - /* Tjmax temperature */ - HWMON_T_LABEL | HWMON_T_INPUT, - /* Core temperature - for all core channels */ - [channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = HWMON_T_LABEL | HWMON_T_INPUT, - 0 -}; - -static const struct hwmon_channel_info peci_cputemp_temp_channel = { - .type = hwmon_temp, - .config = peci_cputemp_temp_channel_config, -}; - static const struct hwmon_channel_info *peci_cputemp_info[] = { - &peci_cputemp_temp_channel, + HWMON_CHANNEL_INFO(temp, + /* Die temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST, + /* DTS margin */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | + HWMON_T_CRIT | HWMON_T_CRIT_HYST, + /* Tcontrol temperature */ + HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_CRIT, + /* Tthrottle temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + /* Tjmax temperature */ + HWMON_T_LABEL | HWMON_T_INPUT, + /* Core temperature - for all core channels */ + [channel_core ... CPUTEMP_CHANNEL_NUMS - 1] = + HWMON_T_LABEL | HWMON_T_INPUT), NULL }; -- cgit v1.2.3 From 7c399d6a5bdf844fb02842e9db3d19d2df911c6a Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:59 -0700 Subject: hwmon: (peci/dimmtemp) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Cc: Iwona Winiarska Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/dimmtemp.c | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index 96b9919db357..3c71b5546bcb 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -300,18 +300,10 @@ static int create_dimm_temp_label(struct peci_dimmtemp *priv, int chan) return 0; } -static const u32 peci_dimmtemp_temp_channel_config[] = { - [0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT, - 0 -}; - -static const struct hwmon_channel_info peci_dimmtemp_temp_channel = { - .type = hwmon_temp, - .config = peci_dimmtemp_temp_channel_config, -}; - static const struct hwmon_channel_info *peci_dimmtemp_temp_info[] = { - &peci_dimmtemp_temp_channel, + HWMON_CHANNEL_INFO(temp, + [0 ... DIMM_NUMS_MAX - 1] = HWMON_T_LABEL | + HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_CRIT), NULL }; -- cgit v1.2.3 From 7282d2aefcc6cc3ab093968df108f399f93e69e8 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:05:59 -0700 Subject: hwmon: (pwm-fan) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index f12b9a28a232..6c08551d8d14 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -49,16 +49,6 @@ struct pwm_fan_ctx { struct hwmon_channel_info fan_channel; }; -static const u32 pwm_fan_channel_config_pwm[] = { - HWMON_PWM_INPUT, - 0 -}; - -static const struct hwmon_channel_info pwm_fan_channel_pwm = { - .type = hwmon_pwm, - .config = pwm_fan_channel_config_pwm, -}; - /* This handler assumes self resetting edge triggered interrupt. */ static irqreturn_t pulse_handler(int irq, void *dev_id) { @@ -387,7 +377,7 @@ static int pwm_fan_probe(struct platform_device *pdev) if (!channels) return -ENOMEM; - channels[0] = &pwm_fan_channel_pwm; + channels[0] = HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT); for (i = 0; i < ctx->tach_count; i++) { struct pwm_fan_tach *tach = &ctx->tachs[i]; -- cgit v1.2.3 From ca538531c0deaeff352f1aa8caa216794cb14bc9 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 12 May 2022 12:06:00 -0700 Subject: hwmon: (sl28cpld-hwmon) Use HWMON_CHANNEL_INFO macro The HWMON_CHANNEL_INFO macro simplifies the code, reduces the likelihood of errors, and makes the code easier to read. The conversion was done automatically with coccinelle. The semantic patch used to make this change is as follows. @s@ identifier i,j,ty; @@ -struct hwmon_channel_info j = { - .type = ty, - .config = i, -}; @r@ initializer list elements; identifier s.i; @@ -u32 i[] = { - elements, - 0 -}; @script:ocaml t@ ty << s.ty; elements << r.elements; shorter; elems; @@ shorter := make_ident (List.hd(List.rev (Str.split (Str.regexp "_") ty))); elems := make_ident (String.concat "," (List.map (fun x -> Printf.sprintf "\n\t\t\t %s" x) (Str.split (Str.regexp " , ") elements))) @@ identifier s.j,t.shorter; identifier t.elems; @@ - &j + HWMON_CHANNEL_INFO(shorter,elems) This patch does not introduce functional changes. Many thanks to Julia Lawall for providing the coccinelle script. Cc: Michael Walle Signed-off-by: Guenter Roeck --- drivers/hwmon/sl28cpld-hwmon.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/sl28cpld-hwmon.c b/drivers/hwmon/sl28cpld-hwmon.c index e48f58ec5b9c..e2fc60bf40bd 100644 --- a/drivers/hwmon/sl28cpld-hwmon.c +++ b/drivers/hwmon/sl28cpld-hwmon.c @@ -67,18 +67,8 @@ static int sl28cpld_hwmon_read(struct device *dev, return 0; } -static const u32 sl28cpld_hwmon_fan_config[] = { - HWMON_F_INPUT, - 0 -}; - -static const struct hwmon_channel_info sl28cpld_hwmon_fan = { - .type = hwmon_fan, - .config = sl28cpld_hwmon_fan_config, -}; - static const struct hwmon_channel_info *sl28cpld_hwmon_info[] = { - &sl28cpld_hwmon_fan, + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), NULL }; -- cgit v1.2.3 From 5b4285c57b6f9b41f6c1def6b624da3a9b000dc6 Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Tue, 17 May 2022 10:05:08 +0200 Subject: hwmon: (asus-ec-sensors) fix Formula VIII definition The ROG CROSSHAIR VIII FORMULA board has the same sensors as the CROSSHAIR VIII HERO [1] thus let's join their definitions which adds missing sensors for Formula. [1] https://github.com/LibreHardwareMonitor/LibreHardwareMonitor/pull/740 Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20220517080508.1910953-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 611e897429b1..57e11b2bab74 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -54,7 +54,7 @@ static char *mutex_path_override; /* ACPI mutex for locking access to the EC for the firmware */ #define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" -#define MAX_IDENTICAL_BOARD_VARIATIONS 2 +#define MAX_IDENTICAL_BOARD_VARIATIONS 3 /* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */ #define ACPI_GLOBAL_LOCK_PSEUDO_PATH ":GLOBAL_LOCK" @@ -257,17 +257,9 @@ static const struct ec_board_info board_info[] = { .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, .family = family_amd_500_series, }, - { - .board_names = {"ROG CROSSHAIR VIII FORMULA"}, - .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | - SENSOR_FAN_CPU_OPT | SENSOR_FAN_CHIPSET | - SENSOR_CURR_CPU | SENSOR_IN_CPU_CORE, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_amd_500_series, - }, { .board_names = { + "ROG CROSSHAIR VIII FORMULA" "ROG CROSSHAIR VIII HERO", "ROG CROSSHAIR VIII HERO (WI-FI)", }, -- cgit v1.2.3 From 718fbfa5da5da0b4d030e14ed737c228ca6a7cbf Mon Sep 17 00:00:00 2001 From: keliu Date: Tue, 17 May 2022 06:31:25 +0000 Subject: hwmon: Directly use ida_alloc()/free() Use ida_alloc()/ida_free() instead of deprecated ida_simple_get()/ida_simple_remove() . Signed-off-by: keliu Link: https://lore.kernel.org/r/20220517063126.2142637-1-liuke94@huawei.com Signed-off-by: Guenter Roeck --- drivers/hwmon/hwmon.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 22de7a9e7ba7..2e2cd79d89eb 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -764,7 +764,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, "hwmon: '%s' is not a valid name attribute, please fix\n", name); - id = ida_simple_get(&hwmon_ida, 0, 0, GFP_KERNEL); + id = ida_alloc(&hwmon_ida, GFP_KERNEL); if (id < 0) return ERR_PTR(id); @@ -856,7 +856,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, free_hwmon: hwmon_dev_release(hdev); ida_remove: - ida_simple_remove(&hwmon_ida, id); + ida_free(&hwmon_ida, id); return ERR_PTR(err); } @@ -968,7 +968,7 @@ void hwmon_device_unregister(struct device *dev) if (likely(sscanf(dev_name(dev), HWMON_ID_FORMAT, &id) == 1)) { device_unregister(dev); - ida_simple_remove(&hwmon_ida, id); + ida_free(&hwmon_ida, id); } else dev_dbg(dev->parent, "hwmon_device_unregister() failed: bad class ID!\n"); -- cgit v1.2.3 From be1ca367e7b6dcf0fe47fe577c65cabc305bbf87 Mon Sep 17 00:00:00 2001 From: keliu Date: Tue, 17 May 2022 06:31:26 +0000 Subject: hwmon: (ibmaem) Directly use ida_alloc()/free() Use ida_alloc()/ida_free() instead of deprecated ida_simple_get()/ida_simple_remove() . Signed-off-by: keliu Link: https://lore.kernel.org/r/20220517063126.2142637-2-liuke94@huawei.com [groeck: Updated subject to include driver name] Signed-off-by: Guenter Roeck --- drivers/hwmon/ibmaem.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/ibmaem.c b/drivers/hwmon/ibmaem.c index de6baf6ca3d1..5c4cf742f5ae 100644 --- a/drivers/hwmon/ibmaem.c +++ b/drivers/hwmon/ibmaem.c @@ -482,7 +482,7 @@ static void aem_delete(struct aem_data *data) ipmi_destroy_user(data->ipmi.user); platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); kfree(data); } @@ -539,7 +539,7 @@ static int aem_init_aem1_inst(struct aem_ipmi_data *probe, u8 module_handle) data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; /* Create sub-device for this fw instance */ - data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + data->id = ida_alloc(&aem_ida, GFP_KERNEL); if (data->id < 0) goto id_err; @@ -600,7 +600,7 @@ ipmi_err: platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); dev_err: - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); id_err: kfree(data); @@ -679,7 +679,7 @@ static int aem_init_aem2_inst(struct aem_ipmi_data *probe, data->power_period[i] = AEM_DEFAULT_POWER_INTERVAL; /* Create sub-device for this fw instance */ - data->id = ida_simple_get(&aem_ida, 0, 0, GFP_KERNEL); + data->id = ida_alloc(&aem_ida, GFP_KERNEL); if (data->id < 0) goto id_err; @@ -740,7 +740,7 @@ ipmi_err: platform_set_drvdata(data->pdev, NULL); platform_device_unregister(data->pdev); dev_err: - ida_simple_remove(&aem_ida, data->id); + ida_free(&aem_ida, data->id); id_err: kfree(data); -- cgit v1.2.3 From 45988d907859b8e6c620c7b8742f65f5b1fbb565 Mon Sep 17 00:00:00 2001 From: Holger Brunck Date: Tue, 17 May 2022 15:56:14 +0200 Subject: hwmon: (lm90) enable extended range according to DTS node Some lm90 compatible devices can operate in an extended temperature mode. This feature is now enabled if the property is set in the corresponding device tree node. Signed-off-by: Holger Brunck Reviewed-by: Guenter Roeck Link: https://lore.kernel.org/r/20220517135614.8185-2-holger.brunck@hitachienergy.com Signed-off-by: Guenter Roeck --- drivers/hwmon/lm90.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/hwmon/lm90.c b/drivers/hwmon/lm90.c index 1c9493c70813..3820f0e61510 100644 --- a/drivers/hwmon/lm90.c +++ b/drivers/hwmon/lm90.c @@ -1707,6 +1707,7 @@ static void lm90_restore_conf(void *_data) static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) { + struct device_node *np = client->dev.of_node; int config, convrate; convrate = lm90_read_reg(client, LM90_REG_R_CONVRATE); @@ -1727,6 +1728,9 @@ static int lm90_init_client(struct i2c_client *client, struct lm90_data *data) /* Check Temperature Range Select */ if (data->flags & LM90_HAVE_EXTENDED_TEMP) { + if (of_property_read_bool(np, "ti,extended-range-enable")) + config |= 0x04; + if (config & 0x04) data->flags |= LM90_FLAG_ADT7461_EXT; } -- cgit v1.2.3 From 9baabde04de64137e86b39112c6259f3da512bd6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Tue, 17 May 2022 13:08:25 -0700 Subject: hwmon: (dimmtemp) Fix bitmap handling Building arm:allmodconfig may fail with the following error. In function 'fortify_memcpy_chk', inlined from 'bitmap_copy' at include/linux/bitmap.h:261:2, inlined from 'bitmap_copy_clear_tail' at include/linux/bitmap.h:270:2, inlined from 'bitmap_from_u64' at include/linux/bitmap.h:622:2, inlined from 'check_populated_dimms' at drivers/hwmon/peci/dimmtemp.c:284:2: include/linux/fortify-string.h:344:25: error: call to '__write_overflow_field' declared with attribute warning: detected write beyond size of field (1st parameter) The problematic code is bitmap_from_u64(priv->dimm_mask, dimm_mask); dimm_mask is declared as u64, but the bitmap in priv->dimm_mask is only 24 bit wide. On 32-bit systems, this results in writes over the end of the bitmap. Fix the problem by using u32 instead of u64 for dimm_mask. This is currently sufficient, and a compile time check to ensure that the number of dimms does not exceed the bit map size is already in place. Fixes: 73bc1b885dae ("hwmon: peci: Add dimmtemp driver") Cc: Iwona Winiarska Reviewed-by: Iwona Winiarska Signed-off-by: Guenter Roeck --- drivers/hwmon/peci/dimmtemp.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/peci/dimmtemp.c b/drivers/hwmon/peci/dimmtemp.c index 3c71b5546bcb..0a633bda3668 100644 --- a/drivers/hwmon/peci/dimmtemp.c +++ b/drivers/hwmon/peci/dimmtemp.c @@ -220,7 +220,7 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) int chan_rank_max = priv->gen_info->chan_rank_max; int dimm_idx_max = priv->gen_info->dimm_idx_max; u32 chan_rank_empty = 0; - u64 dimm_mask = 0; + u32 dimm_mask = 0; int chan_rank, dimm_idx, ret; u32 pcs; @@ -279,9 +279,9 @@ static int check_populated_dimms(struct peci_dimmtemp *priv) return -EAGAIN; } - dev_dbg(priv->dev, "Scanned populated DIMMs: %#llx\n", dimm_mask); + dev_dbg(priv->dev, "Scanned populated DIMMs: %#x\n", dimm_mask); - bitmap_from_u64(priv->dimm_mask, dimm_mask); + bitmap_from_arr32(priv->dimm_mask, &dimm_mask, DIMM_NUMS_MAX); return 0; } -- cgit v1.2.3 From d1baf7a3a3177d46a7149858beddb88a9eca7a54 Mon Sep 17 00:00:00 2001 From: Adam Wujek Date: Thu, 19 May 2022 23:34:01 +0000 Subject: hwmon: (pmbus) Check PEC support before reading other registers Make sure that the support of PEC is determined before the read of other registers. Otherwise the validation of PEC can trigger an error on the read of STATUS_BYTE or STATUS_WORD registers. The problematic scenario is the following. A device with enabled PEC support is up and running and a kernel driver is loaded. Then the driver is unloaded (or device unbound), the HW device is reconfigured externally (e.g. by i2cset) to advertise itself as not supporting PEC. Without the move of the code, at the second load of the driver (or bind) the STATUS_BYTE or STATUS_WORD register is always read with PEC enabled, which is likely to cause a read error resulting with fail of a driver load (or bind). Signed-off-by: Adam Wujek Link: https://lore.kernel.org/r/20220519233334.438621-1-dev_public@wujek.eu Fixes: 75d2b2b06bd84 ("hwmon: (pmbus) disable PEC if not enabled") Fixes: 4e5418f787ec5 ("hwmon: (pmbus_core) Check adapter PEC support") [groeck: Added Fixes: tags, dropped continuation line] Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/pmbus_core.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/pmbus/pmbus_core.c b/drivers/hwmon/pmbus/pmbus_core.c index cf0e77383898..02912022853d 100644 --- a/drivers/hwmon/pmbus/pmbus_core.c +++ b/drivers/hwmon/pmbus/pmbus_core.c @@ -2394,6 +2394,21 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, struct device *dev = &client->dev; int page, ret; + /* + * Figure out if PEC is enabled before accessing any other register. + * Make sure PEC is disabled, will be enabled later if needed. + */ + client->flags &= ~I2C_CLIENT_PEC; + + /* Enable PEC if the controller and bus supports it */ + if (!(data->flags & PMBUS_NO_CAPABILITY)) { + ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); + if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { + if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) + client->flags |= I2C_CLIENT_PEC; + } + } + /* * Some PMBus chips don't support PMBUS_STATUS_WORD, so try * to use PMBUS_STATUS_BYTE instead if that is the case. @@ -2412,19 +2427,6 @@ static int pmbus_init_common(struct i2c_client *client, struct pmbus_data *data, data->has_status_word = true; } - /* Make sure PEC is disabled, will be enabled later if needed */ - client->flags &= ~I2C_CLIENT_PEC; - - /* Enable PEC if the controller and bus supports it */ - if (!(data->flags & PMBUS_NO_CAPABILITY)) { - ret = i2c_smbus_read_byte_data(client, PMBUS_CAPABILITY); - if (ret >= 0 && (ret & PB_CAPABILITY_ERROR_CHECK)) { - if (i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_PEC)) { - client->flags |= I2C_CLIENT_PEC; - } - } - } - /* * Check if the chip is write protected. If it is, we can not clear * faults, and we should not try it. Also, in that case, writes into -- cgit v1.2.3 From 5ab312b3a8fef467e807e8d7672e65cabe2376fd Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Sat, 21 May 2022 13:10:47 +0200 Subject: hwmon: (sl28cpld) Fix typo in comment Spelling mistake (triple letters) in comment. Detected with the help of Coccinelle. Signed-off-by: Julia Lawall Link: https://lore.kernel.org/r/20220521111145.81697-37-Julia.Lawall@inria.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/sl28cpld-hwmon.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/hwmon/sl28cpld-hwmon.c b/drivers/hwmon/sl28cpld-hwmon.c index e2fc60bf40bd..9ce4899a81a5 100644 --- a/drivers/hwmon/sl28cpld-hwmon.c +++ b/drivers/hwmon/sl28cpld-hwmon.c @@ -54,7 +54,7 @@ static int sl28cpld_hwmon_read(struct device *dev, /* * The counter period is 1000ms and the sysfs specification - * says we should asssume 2 pulses per revolution. + * says we should assume 2 pulses per revolution. */ value *= 60 / 2; -- cgit v1.2.3 From 8877ecb0fc8d7662218a8e7ebb0650f320467935 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 22 May 2022 15:14:23 +0200 Subject: hwmon: (aquacomputer_d5next) Fix an error handling path in aqc_probe() If no memory can be allocated, some resources still need to be released as already done in the other error handling paths. Fixes: 752b927951ea ("hwmon: (aquacomputer_d5next) Add support for Aquacomputer Octo") Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/be6b955d50de140fcc96bd116150b435021bf567.1653225250.git.christophe.jaillet@wanadoo.fr Reviewed-by: Aleksa Savic Signed-off-by: Guenter Roeck --- drivers/hwmon/aquacomputer_d5next.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/hwmon/aquacomputer_d5next.c b/drivers/hwmon/aquacomputer_d5next.c index 7d2e7279abfb..a0e69f7ece36 100644 --- a/drivers/hwmon/aquacomputer_d5next.c +++ b/drivers/hwmon/aquacomputer_d5next.c @@ -783,8 +783,10 @@ static int aqc_probe(struct hid_device *hdev, const struct hid_device_id *id) priv->name = aqc_device_names[priv->kind]; priv->buffer = devm_kzalloc(&hdev->dev, priv->buffer_size, GFP_KERNEL); - if (!priv->buffer) - return -ENOMEM; + if (!priv->buffer) { + ret = -ENOMEM; + goto fail_and_close; + } mutex_init(&priv->mutex); -- cgit v1.2.3