From f116af2eb51ed9df24911537fda32a033f1c58da Mon Sep 17 00:00:00 2001 From: Avadhut Naik Date: Tue, 29 Jul 2025 00:15:43 +0000 Subject: hwmon: (k10temp) Add thermal support for AMD Family 1Ah-based models Add thermal info support for newer AMD Family 1Ah-based models. Signed-off-by: Avadhut Naik Link: https://lore.kernel.org/r/20250729001644.257645-1-avadhut.naik@amd.com Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index babf2413d666..2f90a2e9ad49 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -84,6 +84,13 @@ static DEFINE_MUTEX(nb_smu_ind_mutex); */ #define AMD_I3255_STR "3255" +/* + * PCI Device IDs for AMD's Family 1Ah-based SOCs. + * Defining locally as IDs are not shared. + */ +#define PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3 0x12cb +#define PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3 0x127b + struct k10temp_data { struct pci_dev *pdev; void (*read_htcreg)(struct pci_dev *pdev, u32 *regval); @@ -556,7 +563,9 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_19H_M78H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M00H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M20H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, {} }; -- cgit v1.2.3 From 25b2c02e5b1f834b405a7f3fa6854115c8245d4c Mon Sep 17 00:00:00 2001 From: Lucas Yunkyu Lee Date: Mon, 28 Jul 2025 22:49:08 +0200 Subject: hwmon: (asus-ec-sensors) Add STRIX B850-I GAMING WIFI Add support for the STRIX B850-I GAMING WIFI Signed-off-by: Lucas Yunkyu Lee Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250728205133.15487-2-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index de2f2985f06f..558755f9fdd5 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -25,6 +25,7 @@ Supported boards: * ROG MAXIMUS Z690 FORMULA * ROG STRIX B550-E GAMING * ROG STRIX B550-I GAMING + * ROG STRIX B850-I GAMING WIFI * ROG STRIX X570-E GAMING * ROG STRIX X570-E GAMING WIFI II * ROG STRIX X570-F GAMING diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 4ac554731e98..4d8b887b8a55 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -495,6 +495,13 @@ static const struct ec_board_info board_info_strix_b550_i_gaming = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_800_series, +}; + static const struct ec_board_info board_info_strix_x570_e_gaming = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -628,6 +635,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_b550_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &board_info_strix_b550_i_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI", + &board_info_strix_b850_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", &board_info_strix_x570_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING WIFI II", -- cgit v1.2.3 From 32afccb263e48164e4f1596bdf4a9a67468ba330 Mon Sep 17 00:00:00 2001 From: Dylan Tackoor Date: Mon, 28 Jul 2025 22:49:09 +0200 Subject: hwmon: (asus-ec-sensors) Add B650E-I Add support for ROG STRIX B650E-I GAMING WIFI. Signed-off-by: Dylan Tackoor Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250728205133.15487-3-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 558755f9fdd5..1e8274dba35f 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -25,6 +25,7 @@ Supported boards: * ROG MAXIMUS Z690 FORMULA * ROG STRIX B550-E GAMING * ROG STRIX B550-I GAMING + * ROG STRIX B650E-I GAMING WIFI * ROG STRIX B850-I GAMING WIFI * ROG STRIX X570-E GAMING * ROG STRIX X570-E GAMING WIFI II diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 4d8b887b8a55..0b19d148f65d 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -495,6 +495,13 @@ static const struct ec_board_info board_info_strix_b550_i_gaming = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_strix_b650e_i_gaming = { + .sensors = SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | + SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_IN_CPU_CORE, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_strix_b850_i_gaming_wifi = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_MB | SENSOR_TEMP_VRM, @@ -635,6 +642,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_b550_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", &board_info_strix_b550_i_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B650E-I GAMING WIFI", + &board_info_strix_b650e_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B850-I GAMING WIFI", &board_info_strix_b850_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-E GAMING", -- cgit v1.2.3 From 0183cb21b8a87e7499a0ef3b94a7df43e266fa44 Mon Sep 17 00:00:00 2001 From: Nicholas Flintham Date: Mon, 28 Jul 2025 22:49:10 +0200 Subject: hwmon: (asus-ec-sensors) Add ROG STRIX Z790E GAMING WIFI II Add support for the ROG STRIX Z790E GAMING WIFI II board. Signed-off-by: Nicholas Flintham Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250728205133.15487-4-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 1e8274dba35f..da9a00111d1c 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -34,6 +34,7 @@ Supported boards: * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 + * ROG STRIX Z790-E GAMING WIFI II * ROG ZENITH II EXTREME * ROG ZENITH II EXTREME ALPHA * TUF GAMING X670E PLUS diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 0b19d148f65d..b9543eda2522 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -56,6 +56,8 @@ static char *mutex_path_override; #define ASUS_HW_ACCESS_MUTEX_RMTW_ASMX "\\RMTW.ASMX" +#define ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0 "\\_SB.PC00.LPCB.SIO1.MUT0" + #define ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0 "\\_SB_.PCI0.SBRG.SIO1.MUT0" #define MAX_IDENTICAL_BOARD_VARIATIONS 3 @@ -168,7 +170,8 @@ enum board_family { family_amd_800_series, family_intel_300_series, family_intel_400_series, - family_intel_600_series + family_intel_600_series, + family_intel_700_series }; /* @@ -323,6 +326,14 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { EC_SENSOR("Water_Block_In", hwmon_temp, 1, 0x01, 0x02), }; +static const struct ec_sensor_info sensors_family_intel_700[] = { + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x09), + [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), +}; + /* Shortcuts for common combinations */ #define SENSOR_SET_TEMP_CHIPSET_CPU_MB \ (SENSOR_TEMP_CHIPSET | SENSOR_TEMP_CPU | SENSOR_TEMP_MB) @@ -568,6 +579,13 @@ static const struct ec_board_info board_info_strix_z690_a_gaming_wifi_d4 = { .family = family_intel_600_series, }; +static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_zenith_ii_extreme = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | @@ -660,6 +678,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z490_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4", &board_info_strix_z690_a_gaming_wifi_d4), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II", + &board_info_strix_z790_e_gaming_wifi_ii), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME", &board_info_zenith_ii_extreme), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME ALPHA", @@ -1142,6 +1162,9 @@ static int asus_ec_probe(struct platform_device *pdev) case family_intel_600_series: ec_data->sensors_info = sensors_family_intel_600; break; + case family_intel_700_series: + ec_data->sensors_info = sensors_family_intel_700; + break; default: dev_err(dev, "Unknown board family: %d", ec_data->board_info->family); -- cgit v1.2.3 From 6a9b2fb8411e1bc9345f048c8061cfa96f45151f Mon Sep 17 00:00:00 2001 From: Lakshay Piplani Date: Mon, 28 Jul 2025 09:49:12 +0530 Subject: dt-bindings: hwmon: (lm75) Add binding for NXP P3T1750 Add "nxp,p3t1750" to the lm75 compatible list. Signed-off-by: Lakshay Piplani Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250728041913.3754236-1-lakshay.piplani@nxp.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/lm75.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/lm75.yaml b/Documentation/devicetree/bindings/hwmon/lm75.yaml index c38255243f57..ecdd09a032e5 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.yaml +++ b/Documentation/devicetree/bindings/hwmon/lm75.yaml @@ -28,6 +28,7 @@ properties: - maxim,max31725 - maxim,max31726 - maxim,mcp980x + - nxp,p3t1750 - nxp,p3t1755 - nxp,pct2075 - st,stds75 -- cgit v1.2.3 From 83b3354a4ad9a784a3335a2c303c693f521d1e47 Mon Sep 17 00:00:00 2001 From: Lakshay Piplani Date: Mon, 28 Jul 2025 09:49:13 +0530 Subject: hwmon: (lm75) Add NXP P3T1750 support Add support for lm75 compatible NXP P3T1750 temperature sensor. Signed-off-by: Lakshay Piplani Link: https://lore.kernel.org/r/20250728041913.3754236-2-lakshay.piplani@nxp.com [groeck: Fixed alphabetic order for new chip entries] Signed-off-by: Guenter Roeck --- Documentation/hwmon/lm75.rst | 6 ++++-- drivers/hwmon/lm75.c | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/lm75.rst b/Documentation/hwmon/lm75.rst index c6a54bbca3c5..908b3a9df06e 100644 --- a/Documentation/hwmon/lm75.rst +++ b/Documentation/hwmon/lm75.rst @@ -121,9 +121,9 @@ Supported chips: https://www.ti.com/product/TMP1075 - * NXP LM75B, P3T1755, PCT2075 + * NXP LM75B, P3T1750, P3T1755, PCT2075 - Prefix: 'lm75b', 'p3t1755', 'pct2075' + Prefix: 'lm75b', 'p3t1750', 'p3t1755', 'pct2075' Addresses scanned: none @@ -131,6 +131,8 @@ Supported chips: https://www.nxp.com/docs/en/data-sheet/LM75B.pdf + https://www.nxp.com/docs/en/data-sheet/P3T1750DP.pdf + https://www.nxp.com/docs/en/data-sheet/P3T1755.pdf https://www.nxp.com/docs/en/data-sheet/PCT2075.pdf diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index 9b4875e2fd8d..3c23b6e8e1bf 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -39,6 +39,7 @@ enum lm75_type { /* keep sorted in alphabetical order */ max6626, max31725, mcp980x, + p3t1750, p3t1755, pct2075, stds75, @@ -222,6 +223,13 @@ static const struct lm75_params device_params[] = { .default_resolution = 9, .default_sample_time = MSEC_PER_SEC / 18, }, + [p3t1750] = { + .clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */ + .default_resolution = 12, + .default_sample_time = 55, + .num_sample_times = 4, + .sample_times = (unsigned int []){ 28, 55, 110, 220 }, + }, [p3t1755] = { .clr_mask = 1 << 1 | 1 << 7, /* disable SMBAlert and one-shot */ .default_resolution = 12, @@ -805,6 +813,7 @@ static const struct i2c_device_id lm75_i2c_ids[] = { { "max31725", max31725, }, { "max31726", max31725, }, { "mcp980x", mcp980x, }, + { "p3t1750", p3t1750, }, { "p3t1755", p3t1755, }, { "pct2075", pct2075, }, { "stds75", stds75, }, @@ -916,6 +925,10 @@ static const struct of_device_id __maybe_unused lm75_of_match[] = { .compatible = "maxim,mcp980x", .data = (void *)mcp980x }, + { + .compatible = "nxp,p3t1750", + .data = (void *)p3t1750 + }, { .compatible = "nxp,p3t1755", .data = (void *)p3t1755 -- cgit v1.2.3 From fec40c4837a95ae511d9e1b709943b6c243db4d2 Mon Sep 17 00:00:00 2001 From: Runar Grønås Date: Fri, 1 Aug 2025 21:50:08 +0200 Subject: hwmon: (asus-ec-sensors) Add X670E-I GAMING WIFI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for ROG STRIX X670E-I GAMING WIFI Signed-off-by: Runar Grønås Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250801195020.11106-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index da9a00111d1c..49f6cac63d19 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -31,6 +31,7 @@ Supported boards: * ROG STRIX X570-E GAMING WIFI II * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING + * ROG STRIX X670E-I GAMING WIFI * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index b9543eda2522..33c5fcb0a09e 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -553,6 +553,13 @@ static const struct ec_board_info board_info_strix_x570_i_gaming = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_strix_z390_f_gaming = { .sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | @@ -672,6 +679,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x570_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING", &board_info_strix_x570_i_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI", + &board_info_strix_x670e_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", -- cgit v1.2.3 From 15c8317366908c3b5a22573483a3dbbefba3fc38 Mon Sep 17 00:00:00 2001 From: Jamie Vickery Date: Sat, 2 Aug 2025 15:09:02 +0200 Subject: hwmon: (asus-ec-sensors) Add Z790-I GAMING WIFI Add support for the ROG STRIX Z790-I GAMING WIFI board Signed-off-by: Jamie Vickery Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250802130912.175543-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 49f6cac63d19..bedddb6bf9e1 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -36,6 +36,7 @@ Supported boards: * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 * ROG STRIX Z790-E GAMING WIFI II + * ROG STRIX Z790-I GAMING WIFI * ROG ZENITH II EXTREME * ROG ZENITH II EXTREME ALPHA * TUF GAMING X670E PLUS diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 33c5fcb0a09e..e2f7b8705cb1 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -329,6 +329,8 @@ static const struct ec_sensor_info sensors_family_intel_600[] = { static const struct ec_sensor_info sensors_family_intel_700[] = { [ec_sensor_temp_t_sensor] = EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x09), + [ec_sensor_temp_t_sensor_2] = + EC_SENSOR("T_Sensor 2", hwmon_temp, 1, 0x01, 0x05), [ec_sensor_temp_vrm] = EC_SENSOR("VRM", hwmon_temp, 1, 0x00, 0x33), [ec_sensor_fan_cpu_opt] = EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), @@ -593,6 +595,13 @@ static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_T_SENSOR_2 | + SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PC00_LPCB_SIO1_MUT0, + .family = family_intel_700_series, +}; + static const struct ec_board_info board_info_zenith_ii_extreme = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | @@ -689,6 +698,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z690_a_gaming_wifi_d4), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II", &board_info_strix_z790_e_gaming_wifi_ii), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI", + &board_info_strix_z790_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME", &board_info_zenith_ii_extreme), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG ZENITH II EXTREME ALPHA", -- cgit v1.2.3 From 3aa72cf03924d04c8d20f8b319df8f73550dd26c Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Tue, 5 Aug 2025 22:31:48 +0200 Subject: hwmon: (asus-ec-sensors) Narrow lock for X870E-CREATOR WIFI Use mutex from the SIO device rather than the global lock. Signed-off-by: Eugene Shalygin Fixes: 3e538b52157b ("hwmon: (asus-ec-sensors) add ProArt X870E-CREATOR WIFI") Link: https://lore.kernel.org/r/20250805203157.18446-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index e2f7b8705cb1..83047c3263d3 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -409,7 +409,7 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, - .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, .family = family_amd_800_series, }; -- cgit v1.2.3 From ceb562d109ca6ae3ca77d0367ddd374473acbb2d Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Fri, 8 Aug 2025 10:38:07 -0700 Subject: MAINTAINERS: Mark coretemp driver as orphaned This maintainer's email no longer works. Remove it from MAINTAINERS. Also mark the driver as Orphaned for now. Signed-off-by: Dave Hansen Cc: Jean Delvare Cc: Guenter Roeck Cc: linux-hwmon@vger.kernel.org Link: https://lore.kernel.org/r/20250808173807.96D134EA@davehans-spike.ostc.intel.com Signed-off-by: Guenter Roeck --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index cd7ff55b5d32..b69c0271951c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6282,9 +6282,8 @@ F: tools/testing/selftests/cgroup/test_kmem.c F: tools/testing/selftests/cgroup/test_memcontrol.c CORETEMP HARDWARE MONITORING DRIVER -M: Fenghua Yu L: linux-hwmon@vger.kernel.org -S: Maintained +S: Orphan F: Documentation/hwmon/coretemp.rst F: drivers/hwmon/coretemp.c -- cgit v1.2.3 From 3331e5469219d92ea8e9a85fe4fbf679b6aac672 Mon Sep 17 00:00:00 2001 From: ChiShih Tsai Date: Thu, 7 Aug 2025 06:37:23 +0800 Subject: dt-bindings: hwmon: adm1275: add sq24905c support Add support for sq24905c Hot-Swap Controller and Digital Power Monitor. Acked-by: Krzysztof Kozlowski Signed-off-by: ChiShih Tsai Link: https://lore.kernel.org/r/20250806223724.1207-2-tomtsai764@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml index ddb72857c846..d6a7517f2a50 100644 --- a/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml +++ b/Documentation/devicetree/bindings/hwmon/adi,adm1275.yaml @@ -18,6 +18,13 @@ description: | Datasheets: https://www.analog.com/en/products/adm1294.html + The SQ24905C is also a Hot-swap controller compatibility to the ADM1278, + the PMBUS_MFR_MODEL is MC09C + + Datasheets: + https://www.silergy.com/ + download/downloadFile?id=5669&type=product&ftype=note + properties: compatible: enum: @@ -30,6 +37,7 @@ properties: - adi,adm1281 - adi,adm1293 - adi,adm1294 + - silergy,mc09c reg: maxItems: 1 @@ -96,6 +104,7 @@ allOf: - adi,adm1281 - adi,adm1293 - adi,adm1294 + - silergy,mc09c then: properties: adi,volt-curr-sample-average: -- cgit v1.2.3 From 1ba272bfdf55a32c1240a629fd8c98eaaa3f8351 Mon Sep 17 00:00:00 2001 From: ChiShih Tsai Date: Thu, 7 Aug 2025 06:37:24 +0800 Subject: hwmon: (pmbus/adm1275) add sq24905c support Add support for sq24905c which is similar to adm1275 and other chips of the series. Signed-off-by: ChiShih Tsai Link: https://lore.kernel.org/r/20250806223724.1207-3-tomtsai764@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/adm1275.rst | 24 ++++++++++++++++-------- drivers/hwmon/pmbus/Kconfig | 3 ++- drivers/hwmon/pmbus/adm1275.c | 11 ++++++++--- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Documentation/hwmon/adm1275.rst b/Documentation/hwmon/adm1275.rst index 57bd7a850558..cf923f20fa52 100644 --- a/Documentation/hwmon/adm1275.rst +++ b/Documentation/hwmon/adm1275.rst @@ -67,6 +67,14 @@ Supported chips: Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/ADM1293_1294.pdf + * Silergy SQ24905C + + Prefix: 'mc09c' + + Addresses scanned: - + + Datasheet: https://www.silergy.com/download/downloadFile?id=5669&type=product&ftype=note + Author: Guenter Roeck @@ -74,14 +82,14 @@ Description ----------- This driver supports hardware monitoring for Analog Devices ADM1075, ADM1272, -ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and ADM1294 Hot-Swap -Controller and Digital Power Monitors. +ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, ADM1294, and SQ24905C +Hot-Swap Controller and Digital Power Monitors. -ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, and -ADM1294 are hot-swap controllers that allow a circuit board to be removed from -or inserted into a live backplane. They also feature current and voltage -readback via an integrated 12 bit analog-to-digital converter (ADC), accessed -using a PMBus interface. +ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, ADM1293, +ADM1294 and SQ24905C are hot-swap controllers that allow a circuit board to be +removed from or inserted into a live backplane. They also feature current and +voltage readback via an integrated 12 bit analog-to-digital converter (ADC), +accessed using a PMBus interface. The driver is a client driver to the core PMBus driver. Please see Documentation/hwmon/pmbus.rst for details on PMBus client drivers. @@ -160,5 +168,5 @@ temp1_highest Highest observed temperature. temp1_reset_history Write any value to reset history. Temperature attributes are supported on ADM1272, - ADM1273, ADM1278, and ADM1281. + ADM1273, ADM1278, ADM1281 and SQ24905C. ======================= ======================================================= diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 55e492452ce8..77add0c6ee53 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -52,7 +52,8 @@ config SENSORS_ADM1275 help If you say yes here you get hardware monitoring support for Analog Devices ADM1075, ADM1272, ADM1273, ADM1275, ADM1276, ADM1278, ADM1281, - ADM1293, and ADM1294 Hot-Swap Controller and Digital Power Monitors. + ADM1293, ADM1294 and SQ24905C Hot-Swap Controller and + Digital Power Monitors. This driver can also be built as a module. If so, the module will be called adm1275. diff --git a/drivers/hwmon/pmbus/adm1275.c b/drivers/hwmon/pmbus/adm1275.c index 7d175baa5de2..bc2a6a07dc3e 100644 --- a/drivers/hwmon/pmbus/adm1275.c +++ b/drivers/hwmon/pmbus/adm1275.c @@ -18,7 +18,8 @@ #include #include "pmbus.h" -enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, adm1293, adm1294 }; +enum chips { adm1075, adm1272, adm1273, adm1275, adm1276, adm1278, adm1281, + adm1293, adm1294, sq24905c }; #define ADM1275_MFR_STATUS_IOUT_WARN2 BIT(0) #define ADM1293_MFR_STATUS_VAUX_UV_WARN BIT(5) @@ -486,6 +487,7 @@ static const struct i2c_device_id adm1275_id[] = { { "adm1281", adm1281 }, { "adm1293", adm1293 }, { "adm1294", adm1294 }, + { "mc09c", sq24905c }, { } }; MODULE_DEVICE_TABLE(i2c, adm1275_id); @@ -532,7 +534,8 @@ static int adm1275_probe(struct i2c_client *client) dev_err(&client->dev, "Failed to read Manufacturer ID\n"); return ret; } - if (ret != 3 || strncmp(block_buffer, "ADI", 3)) { + if ((ret != 3 || strncmp(block_buffer, "ADI", 3)) && + (ret != 2 || strncmp(block_buffer, "SY", 2))) { dev_err(&client->dev, "Unsupported Manufacturer ID\n"); return -ENODEV; } @@ -558,7 +561,8 @@ static int adm1275_probe(struct i2c_client *client) if (mid->driver_data == adm1272 || mid->driver_data == adm1273 || mid->driver_data == adm1278 || mid->driver_data == adm1281 || - mid->driver_data == adm1293 || mid->driver_data == adm1294) + mid->driver_data == adm1293 || mid->driver_data == adm1294 || + mid->driver_data == sq24905c) config_read_fn = i2c_smbus_read_word_data; else config_read_fn = i2c_smbus_read_byte_data; @@ -708,6 +712,7 @@ static int adm1275_probe(struct i2c_client *client) break; case adm1278: case adm1281: + case sq24905c: data->have_vout = true; data->have_pin_max = true; data->have_temp_max = true; -- cgit v1.2.3 From 71e5262374e7ccc7e692fc005273f1fe2f74830e Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Wed, 27 Aug 2025 11:13:44 +0200 Subject: hwmon: Remove Jean Delvare from maintainers I haven't been active in maintaining the hwmon subsystem in the last decade, so I think it's about time to admit that I do not have the time for this duty and update the MAINTAINERS file to reflect that. I would like to thank Guenter Roeck for taking over and doing an excellent work for so many years. Signed-off-by: Jean Delvare Link: https://lore.kernel.org/r/20250827111344.0debba2a@endymion Signed-off-by: Guenter Roeck --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index b69c0271951c..b392d34deb62 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10717,7 +10717,6 @@ W: http://www.kernel.org/pub/linux/kernel/people/fseidel/hdaps/ F: drivers/platform/x86/hdaps.c HARDWARE MONITORING -M: Jean Delvare M: Guenter Roeck L: linux-hwmon@vger.kernel.org S: Maintained -- cgit v1.2.3 From a6461d2039fac16e7965bf91742f592986803194 Mon Sep 17 00:00:00 2001 From: Liao Yuanhong Date: Wed, 20 Aug 2025 21:15:08 +0800 Subject: hwmon: (ltc4282) remove the use of dev_err_probe() Logging messages that show some type of "out of memory" error are generally unnecessary as there is a generic message and a stack dump done by the memory subsystem. These messages generally increase kernel size without much added value[1]. The dev_err_probe() doesn't do anything when error is '-ENOMEM'. Therefore, remove the useless call to dev_err_probe(), and just return the value instead. [1]: https://lore.kernel.org/lkml/1402419340.30479.18.camel@joe-AO725/ Signed-off-by: Liao Yuanhong Link: https://lore.kernel.org/r/20250820131509.502007-1-liaoyuanhong@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/ltc4282.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/hwmon/ltc4282.c b/drivers/hwmon/ltc4282.c index dbb30abcd343..1d664a2d7b3c 100644 --- a/drivers/hwmon/ltc4282.c +++ b/drivers/hwmon/ltc4282.c @@ -1693,8 +1693,7 @@ static int ltc4282_probe(struct i2c_client *i2c) st = devm_kzalloc(dev, sizeof(*st), GFP_KERNEL); if (!st) - return dev_err_probe(dev, -ENOMEM, - "Failed to allocate memory\n"); + return -ENOMEM; st->map = devm_regmap_init_i2c(i2c, <c4282_regmap_config); if (IS_ERR(st->map)) -- cgit v1.2.3 From fd1a9a68e64fc41bffd8bfbf987a11c53dda4cf2 Mon Sep 17 00:00:00 2001 From: Aleksander Jan Bajkowski Date: Thu, 14 Aug 2025 10:04:41 +0200 Subject: dt-bindings: hwmon: convert lantiq-cputemp to yaml Convert the Lantiq cpu temperature sensor bindings to yaml format. Signed-off-by: Aleksander Jan Bajkowski Reviewed-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250814080708.3054732-1-olek2@wp.pl Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/lantiq,cputemp.yaml | 30 ++++++++++++++++++++++ .../devicetree/bindings/hwmon/ltq-cputemp.txt | 10 -------- 2 files changed, 30 insertions(+), 10 deletions(-) create mode 100644 Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml delete mode 100644 Documentation/devicetree/bindings/hwmon/ltq-cputemp.txt diff --git a/Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml b/Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml new file mode 100644 index 000000000000..9419b481ff35 --- /dev/null +++ b/Documentation/devicetree/bindings/hwmon/lantiq,cputemp.yaml @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/hwmon/lantiq,cputemp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lantiq cpu temperature sensor + +maintainers: + - Florian Eckert + +properties: + compatible: + const: lantiq,cputemp + + reg: + maxItems: 1 + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + cputemp@103040 { + compatible = "lantiq,cputemp"; + reg = <0x103040 0x4>; + }; diff --git a/Documentation/devicetree/bindings/hwmon/ltq-cputemp.txt b/Documentation/devicetree/bindings/hwmon/ltq-cputemp.txt deleted file mode 100644 index 473b34c876dd..000000000000 --- a/Documentation/devicetree/bindings/hwmon/ltq-cputemp.txt +++ /dev/null @@ -1,10 +0,0 @@ -Lantiq cpu temperature sensor - -Requires node properties: -- compatible value : - "lantiq,cputemp" - -Example: - cputemp@0 { - compatible = "lantiq,cputemp"; - }; -- cgit v1.2.3 From e5d1e313d7b6272d6dfda983906d99f97ad9062b Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sun, 24 Aug 2025 02:04:41 +0800 Subject: hwmon: (k10temp) Add device ID for Strix Halo The device ID of Strix Halo Data Fabric Function 3 has been in the tree since commit 0e640f0a47d8 ("x86/amd_nb: Add new PCI IDs for AMD family 0x1a"), but is somehow missing from k10temp_id_table. Add it so that it works out of the box. Tested on Beelink GTR9 Pro Mini PC. Signed-off-by: Rong Zhang Reviewed-by: Mario Limonciello Link: https://lore.kernel.org/r/20250823180443.85512-1-i@rong.moe Signed-off-by: Guenter Roeck --- drivers/hwmon/k10temp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index 2f90a2e9ad49..b98d5ec72c4f 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -565,6 +565,7 @@ static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M20H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M50H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M60H_DF_F3) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M70H_DF_F3) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_1AH_M90H_DF_F3) }, { PCI_VDEVICE(HYGON, PCI_DEVICE_ID_AMD_17H_DF_F3) }, {} -- cgit v1.2.3 From f86e7a69ca9a4a9c2147d2bbff4dd7e025f80713 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Wed, 13 Aug 2025 21:07:28 +0200 Subject: hwmon: (sch56xx-common) don't print superfluous errors The watchdog core will handle error messages already. Signed-off-by: Wolfram Sang Link: https://lore.kernel.org/r/20250813190728.3682-2-wsa+renesas@sang-engineering.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sch56xx-common.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/hwmon/sch56xx-common.c b/drivers/hwmon/sch56xx-common.c index 71941b1bb573..98e075e54e9d 100644 --- a/drivers/hwmon/sch56xx-common.c +++ b/drivers/hwmon/sch56xx-common.c @@ -544,10 +544,8 @@ void sch56xx_watchdog_register(struct device *parent, u16 addr, u32 revision, watchdog_set_drvdata(&data->wddev, data); err = devm_watchdog_register_device(parent, &data->wddev); - if (err) { - pr_err("Registering watchdog chardev: %d\n", err); + if (err) devm_kfree(parent, data); - } } EXPORT_SYMBOL(sch56xx_watchdog_register); -- cgit v1.2.3 From 43c056ac85b60232861005765153707f1b0354b6 Mon Sep 17 00:00:00 2001 From: David Ober Date: Thu, 7 Aug 2025 06:32:28 -0400 Subject: hwmon: (lenovo-ec-sensors) Update P8 supprt This fixes differences for the P8 system that was initially set to the same thermal values as the P7, also adds in the PSU sensor for all of the supported systems Signed-off-by: David Ober Signed-off-by: David Ober Link: https://lore.kernel.org/r/20250807103228.10465-1-dober6023@gmail.com [groeck: Update subject] Signed-off-by: Guenter Roeck --- drivers/hwmon/lenovo-ec-sensors.c | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/drivers/hwmon/lenovo-ec-sensors.c b/drivers/hwmon/lenovo-ec-sensors.c index 143fb79713f7..8681bbf6665b 100644 --- a/drivers/hwmon/lenovo-ec-sensors.c +++ b/drivers/hwmon/lenovo-ec-sensors.c @@ -66,7 +66,7 @@ enum systems { LENOVO_P8, }; -static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; +static int px_temp_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31, 32}; static const char * const lenovo_px_ec_temp_label[] = { "CPU1", @@ -84,9 +84,29 @@ static const char * const lenovo_px_ec_temp_label[] = { "PCI_Z3", "PCI_Z4", "AMB", + "PSU1", + "PSU2", }; -static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; +static int p8_temp_map[] = {0, 1, 2, 8, 9, 13, 14, 15, 16, 17, 19, 20, 33}; + +static const char * const lenovo_p8_ec_temp_label[] = { + "CPU1", + "CPU_DIMM_BANK1", + "CPU_DIMM_BANK2", + "M2_Z2R", + "M2_Z3R", + "DIMM_RIGHT", + "DIMM_LEFT", + "PCI_Z1", + "PCI_Z2", + "PCI_Z3", + "AMB", + "REAR_VR", + "PSU", +}; + +static int gen_temp_map[] = {0, 2, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 31}; static const char * const lenovo_gen_ec_temp_label[] = { "CPU1", @@ -101,6 +121,7 @@ static const char * const lenovo_gen_ec_temp_label[] = { "PCI_Z3", "PCI_Z4", "AMB", + "PSU", }; static int px_fan_map[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; @@ -293,6 +314,8 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_px[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, @@ -327,6 +350,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p8[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, @@ -359,6 +383,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p7[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, @@ -388,6 +413,7 @@ static const struct hwmon_channel_info *lenovo_ec_hwmon_info_p5[] = { HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL, + HWMON_T_INPUT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_LABEL), HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MAX, @@ -545,9 +571,9 @@ static int lenovo_ec_probe(struct platform_device *pdev) break; case 3: ec_data->fan_labels = p8_ec_fan_label; - ec_data->temp_labels = lenovo_gen_ec_temp_label; + ec_data->temp_labels = lenovo_p8_ec_temp_label; ec_data->fan_map = p8_fan_map; - ec_data->temp_map = gen_temp_map; + ec_data->temp_map = p8_temp_map; lenovo_ec_chip_info.info = lenovo_ec_hwmon_info_p8; break; default: -- cgit v1.2.3 From d9d61f1da35038793156c04bb13f0a1350709121 Mon Sep 17 00:00:00 2001 From: Chuande Chen Date: Thu, 14 Aug 2025 13:39:40 +0800 Subject: hwmon: (sbtsi_temp) AMD CPU extended temperature range support Many AMD CPUs can support this feature now. We would get a wrong CPU DIE temperature if don't consider this. In low-temperature environments, the CPU die temperature can drop below zero. So many platforms would like to make extended temperature range as their default configuration. Default temperature range (0C to 255.875C). Extended temperature range (-49C to +206.875C). Ref Doc: AMD V3000 PPR (Doc ID #56558). Signed-off-by: Chuande Chen Link: https://lore.kernel.org/r/20250814053940.96764-1-chenchuande@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sbtsi_temp.c | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/hwmon/sbtsi_temp.c index 3c839f56c460..a6c439e376ff 100644 --- a/drivers/hwmon/sbtsi_temp.c +++ b/drivers/hwmon/sbtsi_temp.c @@ -14,6 +14,7 @@ #include #include #include +#include /* * SB-TSI registers only support SMBus byte data access. "_INT" registers are @@ -29,8 +30,22 @@ #define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */ #define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */ +/* + * Bit for reporting value with temperature measurement range. + * bit == 0: Use default temperature range (0C to 255.875C). + * bit == 1: Use extended temperature range (-49C to +206.875C). + */ +#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2 +/* + * ReadOrder bit specifies the reading order of integer and decimal part of + * CPU temperature for atomic reads. If bit == 0, reading integer part triggers + * latching of the decimal part, so integer part should be read first. + * If bit == 1, read order should be reversed. + */ #define SBTSI_CONFIG_READ_ORDER_SHIFT 5 +#define SBTSI_TEMP_EXT_RANGE_ADJ 49000 + #define SBTSI_TEMP_MIN 0 #define SBTSI_TEMP_MAX 255875 @@ -38,6 +53,8 @@ struct sbtsi_data { struct i2c_client *client; struct mutex lock; + bool ext_range_mode; + bool read_order; }; /* @@ -74,23 +91,11 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type, { struct sbtsi_data *data = dev_get_drvdata(dev); s32 temp_int, temp_dec; - int err; switch (attr) { case hwmon_temp_input: - /* - * ReadOrder bit specifies the reading order of integer and - * decimal part of CPU temp for atomic reads. If bit == 0, - * reading integer part triggers latching of the decimal part, - * so integer part should be read first. If bit == 1, read - * order should be reversed. - */ - err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); - if (err < 0) - return err; - mutex_lock(&data->lock); - if (err & BIT(SBTSI_CONFIG_READ_ORDER_SHIFT)) { + if (data->read_order) { temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC); temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT); } else { @@ -122,6 +127,8 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type, return temp_dec; *val = sbtsi_reg_to_mc(temp_int, temp_dec); + if (data->ext_range_mode) + *val -= SBTSI_TEMP_EXT_RANGE_ADJ; return 0; } @@ -146,6 +153,8 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type, return -EINVAL; } + if (data->ext_range_mode) + val += SBTSI_TEMP_EXT_RANGE_ADJ; val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX); sbtsi_mc_to_reg(val, &temp_int, &temp_dec); @@ -203,6 +212,7 @@ static int sbtsi_probe(struct i2c_client *client) struct device *dev = &client->dev; struct device *hwmon_dev; struct sbtsi_data *data; + int err; data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL); if (!data) @@ -211,8 +221,14 @@ static int sbtsi_probe(struct i2c_client *client) data->client = client; mutex_init(&data->lock); - hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &sbtsi_chip_info, - NULL); + err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG); + if (err < 0) + return err; + data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err); + data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err); + + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, + &sbtsi_chip_info, NULL); return PTR_ERR_OR_ZERO(hwmon_dev); } -- cgit v1.2.3 From 5473fccb809a4cb6777572d420c03446d96101b0 Mon Sep 17 00:00:00 2001 From: Grant Peltier Date: Wed, 27 Aug 2025 17:44:19 -0500 Subject: dt-bindings: hwmon: (pmbus/isl68137) add RAA228244 and RAA228246 support Add device type support for raa228244 and raa228246. Signed-off-by: Grant Peltier Acked-by: Conor Dooley Link: https://lore.kernel.org/r/c0c6e99e51b6fd4c5dbab02e02e4d81abe31f085.1756331945.git.grantpeltier93@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml index 3dc7f15484d2..ae23a05375cb 100644 --- a/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml +++ b/Documentation/devicetree/bindings/hwmon/pmbus/isil,isl68137.yaml @@ -54,6 +54,8 @@ properties: - renesas,raa228004 - renesas,raa228006 - renesas,raa228228 + - renesas,raa228244 + - renesas,raa228246 - renesas,raa229001 - renesas,raa229004 - renesas,raa229621 -- cgit v1.2.3 From 2190ad55a601da0bf78ae9dc14b26e82a1c4c959 Mon Sep 17 00:00:00 2001 From: Grant Peltier Date: Wed, 27 Aug 2025 17:41:36 -0500 Subject: hwmon: (pmbus/isl68137) add support for Renesas RAA228244 and RAA228246 The RAA228244 and RAA228246 are both recently released digital dual-output multiphase PWM controllers. Signed-off-by: Grant Peltier Link: https://lore.kernel.org/r/70bb08e291bd57722b1b0edf732cd0017714ef07.1756331945.git.grantpeltier93@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/pmbus/isl68137.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/hwmon/pmbus/isl68137.c b/drivers/hwmon/pmbus/isl68137.c index c52c55d2e7f4..52cf62e45a86 100644 --- a/drivers/hwmon/pmbus/isl68137.c +++ b/drivers/hwmon/pmbus/isl68137.c @@ -61,6 +61,8 @@ enum chips { raa228004, raa228006, raa228228, + raa228244, + raa228246, raa229001, raa229004, raa229621, @@ -464,6 +466,8 @@ static const struct i2c_device_id raa_dmpvr_id[] = { {"raa228004", raa_dmpvr2_hv}, {"raa228006", raa_dmpvr2_hv}, {"raa228228", raa_dmpvr2_2rail_nontc}, + {"raa228244", raa_dmpvr2_2rail_nontc}, + {"raa228246", raa_dmpvr2_2rail_nontc}, {"raa229001", raa_dmpvr2_2rail}, {"raa229004", raa_dmpvr2_2rail}, {"raa229621", raa_dmpvr2_2rail}, @@ -512,6 +516,8 @@ static const struct of_device_id isl68137_of_match[] = { { .compatible = "renesas,raa228004", .data = (void *)raa_dmpvr2_hv }, { .compatible = "renesas,raa228006", .data = (void *)raa_dmpvr2_hv }, { .compatible = "renesas,raa228228", .data = (void *)raa_dmpvr2_2rail_nontc }, + { .compatible = "renesas,raa228244", .data = (void *)raa_dmpvr2_2rail_nontc }, + { .compatible = "renesas,raa228246", .data = (void *)raa_dmpvr2_2rail_nontc }, { .compatible = "renesas,raa229001", .data = (void *)raa_dmpvr2_2rail }, { .compatible = "renesas,raa229004", .data = (void *)raa_dmpvr2_2rail }, { .compatible = "renesas,raa229621", .data = (void *)raa_dmpvr2_2rail }, -- cgit v1.2.3 From 3d5fcffcdf6266ddf070110eb7d8a6387fd9d291 Mon Sep 17 00:00:00 2001 From: Grant Peltier Date: Wed, 27 Aug 2025 17:42:47 -0500 Subject: docs: hwmon: add RAA228244 and RAA228246 info to isl68137 documentation The Renesas RAA228244 and RAA228246 are recently released digital multiphase controllers. Signed-off-by: Grant Peltier Link: https://lore.kernel.org/r/ddeaf4d2fd1f9c85302ee9b5bf16cfaecf9b89ad.1756331945.git.grantpeltier93@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/isl68137.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Documentation/hwmon/isl68137.rst b/Documentation/hwmon/isl68137.rst index 0e71b22047f8..5bc029c98383 100644 --- a/Documentation/hwmon/isl68137.rst +++ b/Documentation/hwmon/isl68137.rst @@ -374,6 +374,26 @@ Supported chips: Publicly available (after August 2020 launch) at the Renesas website + * Renesas RAA228244 + + Prefix: 'raa228244' + + Addresses scanned: - + + Datasheet: + + Provided by Renesas upon request and NDA + + * Renesas RAA228246 + + Prefix: 'raa228246' + + Addresses scanned: - + + Datasheet: + + Provided by Renesas upon request and NDA + * Renesas RAA229001 Prefix: 'raa229001' -- cgit v1.2.3 From 468a20df2ba62ab55feabc4e4306e70824bcb26c Mon Sep 17 00:00:00 2001 From: Dave Hansen Date: Thu, 28 Aug 2025 13:17:29 -0700 Subject: hwmon: (coretemp) Replace x86_model checks with VFM ones Intel CPUs have been using Family 6 for a while. The Family-model checks in the coretemp driver implicitly assume Family 6. With the upcoming Family 18 and 19 models, some of these checks fall apart. While reading the temperature target MSR, cpu_has_tjmax() performs model checks only to determine if a device warning should be printed. Instead of expanding the checks, get rid of the function and print the warning once unconditionally if the MSR read fails. The checks aren't worth preventing a single line warning to dmesg. Update the rest of the x86_model checks with VFM ones to make them more robust. This automatically covers the upcoming Family 18 and 19 as well as any future extended families. Add a code comment to reflect that none of the CPUs in Family 5 or Family 15 set X86_FEATURE_DTHERM. The VFM checks do not impact these CPUs since the driver does not load on them. Signed-off-by: Dave Hansen Signed-off-by: Sohil Mehta Reviewed-by: Dave Hansen Link: https://lore.kernel.org/r/20250828201729.1145420-1-sohil.mehta@intel.com Signed-off-by: Guenter Roeck --- drivers/hwmon/coretemp.c | 76 +++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 40 deletions(-) diff --git a/drivers/hwmon/coretemp.c b/drivers/hwmon/coretemp.c index 1b9203b20d70..ad79db5a183e 100644 --- a/drivers/hwmon/coretemp.c +++ b/drivers/hwmon/coretemp.c @@ -122,29 +122,29 @@ static const struct tjmax tjmax_table[] = { }; struct tjmax_model { - u8 model; - u8 mask; + u32 vfm; + u8 stepping_mask; int tjmax; }; #define ANY 0xff static const struct tjmax_model tjmax_model_table[] = { - { 0x1c, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */ - { 0x1c, ANY, 90000 }, /* Z5xx, N2xx, possibly others - * Note: Also matches 230 and 330, - * which are covered by tjmax_table - */ - { 0x26, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx) - * Note: TjMax for E6xxT is 110C, but CPU type - * is undetectable by software - */ - { 0x27, ANY, 90000 }, /* Atom Medfield (Z2460) */ - { 0x35, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */ - { 0x36, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx) - * Also matches S12x0 (stepping 9), covered by - * PCI table - */ + { INTEL_ATOM_BONNELL, 10, 100000 }, /* D4xx, K4xx, N4xx, D5xx, K5xx, N5xx */ + { INTEL_ATOM_BONNELL, ANY, 90000 }, /* Z5xx, N2xx, possibly others + * Note: Also matches 230 and 330, + * which are covered by tjmax_table + */ + { INTEL_ATOM_BONNELL_MID, ANY, 90000 }, /* Atom Tunnel Creek (Exx), Lincroft (Z6xx) + * Note: TjMax for E6xxT is 110C, but CPU type + * is undetectable by software + */ + { INTEL_ATOM_SALTWELL_MID, ANY, 90000 }, /* Atom Medfield (Z2460) */ + { INTEL_ATOM_SALTWELL_TABLET, ANY, 90000 }, /* Atom Clover Trail/Cloverview (Z27x0) */ + { INTEL_ATOM_SALTWELL, ANY, 100000 }, /* Atom Cedar Trail/Cedarview (N2xxx, D2xxx) + * Also matches S12x0 (stepping 9), covered by + * PCI table + */ }; static bool is_pkg_temp_data(struct temp_data *tdata) @@ -180,6 +180,11 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) } pci_dev_put(host_bridge); + /* + * This is literally looking for "CPU XXX" in the model string. + * Not checking it against the model as well. Just purely a + * string search. + */ for (i = 0; i < ARRAY_SIZE(tjmax_table); i++) { if (strstr(c->x86_model_id, tjmax_table[i].id)) return tjmax_table[i].tjmax; @@ -187,17 +192,18 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) for (i = 0; i < ARRAY_SIZE(tjmax_model_table); i++) { const struct tjmax_model *tm = &tjmax_model_table[i]; - if (c->x86_model == tm->model && - (tm->mask == ANY || c->x86_stepping == tm->mask)) + if (c->x86_vfm == tm->vfm && + (tm->stepping_mask == ANY || + tm->stepping_mask == c->x86_stepping)) return tm->tjmax; } /* Early chips have no MSR for TjMax */ - if (c->x86_model == 0xf && c->x86_stepping < 4) + if (c->x86_vfm == INTEL_CORE2_MEROM && c->x86_stepping < 4) usemsr_ee = 0; - if (c->x86_model > 0xe && usemsr_ee) { + if (c->x86_vfm > INTEL_CORE_YONAH && usemsr_ee) { u8 platform_id; /* @@ -211,7 +217,8 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) "Unable to access MSR 0x17, assuming desktop" " CPU\n"); usemsr_ee = 0; - } else if (c->x86_model < 0x17 && !(eax & 0x10000000)) { + } else if (c->x86_vfm < INTEL_CORE2_PENRYN && + !(eax & 0x10000000)) { /* * Trust bit 28 up to Penryn, I could not find any * documentation on that; if you happen to know @@ -226,7 +233,7 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) * Mobile Penryn CPU seems to be platform ID 7 or 5 * (guesswork) */ - if (c->x86_model == 0x17 && + if (c->x86_vfm == INTEL_CORE2_PENRYN && (platform_id == 5 || platform_id == 7)) { /* * If MSR EE bit is set, set it to 90 degrees C, @@ -258,18 +265,6 @@ static int adjust_tjmax(struct cpuinfo_x86 *c, u32 id, struct device *dev) return tjmax; } -static bool cpu_has_tjmax(struct cpuinfo_x86 *c) -{ - u8 model = c->x86_model; - - return model > 0xe && - model != 0x1c && - model != 0x26 && - model != 0x27 && - model != 0x35 && - model != 0x36; -} - static int get_tjmax(struct temp_data *tdata, struct device *dev) { struct cpuinfo_x86 *c = &cpu_data(tdata->cpu); @@ -287,8 +282,7 @@ static int get_tjmax(struct temp_data *tdata, struct device *dev) */ err = rdmsr_safe_on_cpu(tdata->cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx); if (err) { - if (cpu_has_tjmax(c)) - dev_warn(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu); + dev_warn_once(dev, "Unable to read TjMax from CPU %u\n", tdata->cpu); } else { val = (eax >> 16) & 0xff; if (val) @@ -460,7 +454,7 @@ static int chk_ucode_version(unsigned int cpu) * Readings might stop update when processor visited too deep sleep, * fixed for stepping D0 (6EC). */ - if (c->x86_model == 0xe && c->x86_stepping < 0xc && c->microcode < 0x39) { + if (c->x86_vfm == INTEL_CORE_YONAH && c->x86_stepping < 0xc && c->microcode < 0x39) { pr_err("Errata AE18 not fixed, update BIOS or microcode of the CPU!\n"); return -ENODEV; } @@ -580,7 +574,7 @@ static int create_core_data(struct platform_device *pdev, unsigned int cpu, * MSR_IA32_TEMPERATURE_TARGET register. Atoms don't have the register * at all. */ - if (c->x86_model > 0xe && c->x86_model != 0x1c) + if (c->x86_vfm > INTEL_CORE_YONAH && c->x86_vfm != INTEL_ATOM_BONNELL) if (get_ttarget(tdata, &pdev->dev) >= 0) tdata->attr_size++; @@ -793,7 +787,9 @@ static int __init coretemp_init(void) /* * CPUID.06H.EAX[0] indicates whether the CPU has thermal * sensors. We check this bit only, all the early CPUs - * without thermal sensors will be filtered out. + * without thermal sensors will be filtered out. This + * includes all the Family 5 and Family 15 (Pentium 4) + * models, since they never set the CPUID bit. */ if (!x86_match_cpu(coretemp_ids)) return -ENODEV; -- cgit v1.2.3 From d5c42cb4583c59e24262f0227fe9a0bf35a661e1 Mon Sep 17 00:00:00 2001 From: Qianfeng Rong Date: Sat, 30 Aug 2025 17:51:05 +0800 Subject: hwmon: (nct6775) Use int type to store negative error codes Change the 'ret' variable from u32 to int in nct6775_asuswmi_read() to store negative error codes or zero; Storing the negative error codes in unsigned type, doesn't cause an issue at runtime but can be confusing. Additionally, assigning negative error codes to unsigned type may trigger a GCC warning when the -Wsign-conversion flag is enabled. No effect on runtime. Signed-off-by: Qianfeng Rong Link: https://lore.kernel.org/r/20250830095105.3271-1-rongqianfeng@vivo.com Signed-off-by: Guenter Roeck --- drivers/hwmon/nct6775-platform.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c index 0a040364b512..407945d2cd6a 100644 --- a/drivers/hwmon/nct6775-platform.c +++ b/drivers/hwmon/nct6775-platform.c @@ -167,7 +167,8 @@ static inline int nct6775_asuswmi_write(u8 bank, u8 reg, u8 val) static inline int nct6775_asuswmi_read(u8 bank, u8 reg, u8 *val) { - u32 ret, tmp = 0; + u32 tmp = 0; + int ret; ret = nct6775_asuswmi_evaluate_method(ASUSWMI_METHODID_RHWM, bank, reg, 0, &tmp); -- cgit v1.2.3 From 9d4388466768c65691e6d554e89a0e57528c9edb Mon Sep 17 00:00:00 2001 From: Michael Tandy Date: Sat, 30 Aug 2025 14:00:59 +0200 Subject: (asus-ec-sensors) add Pro WS WRX90E-SAGE SE Add support for Pro WS WRX90E-SAGE SE Signed-off-by: Michael Tandy Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250830120121.738223-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index bedddb6bf9e1..12c6b5b266bb 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -9,6 +9,7 @@ Supported boards: * PRIME X570-PRO * PRIME X670E-PRO WIFI * Pro WS X570-ACE + * Pro WS WRX90E-SAGE SE * ProArt X570-CREATOR WIFI * ProArt X670E-CREATOR WIFI * ProArt X870E-CREATOR WIFI diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 83047c3263d3..09a751b44ee2 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -117,10 +117,18 @@ enum ec_sensors { ec_sensor_fan_cpu_opt, /* VRM heat sink fan [RPM] */ ec_sensor_fan_vrm_hs, + /* VRM east heat sink fan [RPM] */ + ec_sensor_fan_vrme_hs, + /* VRM west heat sink fan [RPM] */ + ec_sensor_fan_vrmw_hs, /* Chipset fan [RPM] */ ec_sensor_fan_chipset, /* Water flow sensor reading [RPM] */ ec_sensor_fan_water_flow, + /* USB4 fan [RPM] */ + ec_sensor_fan_usb4, + /* M.2 fan [RPM] */ + ec_sensor_fan_m2, /* CPU current [A] */ ec_sensor_curr_cpu, /* "Water_In" temperature sensor reading [℃] */ @@ -150,8 +158,12 @@ enum ec_sensors { #define SENSOR_IN_CPU_CORE BIT(ec_sensor_in_cpu_core) #define SENSOR_FAN_CPU_OPT BIT(ec_sensor_fan_cpu_opt) #define SENSOR_FAN_VRM_HS BIT(ec_sensor_fan_vrm_hs) +#define SENSOR_FAN_VRME_HS BIT(ec_sensor_fan_vrme_hs) +#define SENSOR_FAN_VRMW_HS BIT(ec_sensor_fan_vrmw_hs) #define SENSOR_FAN_CHIPSET BIT(ec_sensor_fan_chipset) #define SENSOR_FAN_WATER_FLOW BIT(ec_sensor_fan_water_flow) +#define SENSOR_FAN_USB4 BIT(ec_sensor_fan_usb4) +#define SENSOR_FAN_M2 BIT(ec_sensor_fan_m2) #define SENSOR_CURR_CPU BIT(ec_sensor_curr_cpu) #define SENSOR_TEMP_WATER_IN BIT(ec_sensor_temp_water_in) #define SENSOR_TEMP_WATER_OUT BIT(ec_sensor_temp_water_out) @@ -168,6 +180,7 @@ enum board_family { family_amd_500_series, family_amd_600_series, family_amd_800_series, + family_amd_wrx_90, family_intel_300_series, family_intel_400_series, family_intel_600_series, @@ -278,6 +291,21 @@ static const struct ec_sensor_info sensors_family_amd_800[] = { EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), }; +static const struct ec_sensor_info sensors_family_amd_wrx_90[] = { + [ec_sensor_temp_cpu_package] = + EC_SENSOR("CPU Package", hwmon_temp, 1, 0x00, 0x31), + [ec_sensor_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xb0), + [ec_sensor_fan_vrmw_hs] = + EC_SENSOR("VRMW HS", hwmon_fan, 2, 0x00, 0xb4), + [ec_sensor_fan_usb4] = EC_SENSOR("USB4", hwmon_fan, 2, 0x00, 0xb6), + [ec_sensor_fan_vrme_hs] = + EC_SENSOR("VRME HS", hwmon_fan, 2, 0x00, 0xbc), + [ec_sensor_fan_m2] = EC_SENSOR("M.2", hwmon_fan, 2, 0x00, 0xbe), + [ec_sensor_temp_t_sensor] = + EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04), +}; + static const struct ec_sensor_info sensors_family_intel_300[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), @@ -421,6 +449,15 @@ static const struct ec_board_info board_info_pro_art_b550_creator = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_pro_ws_wrx90e_sage_se = { + /* Board also has a nct6798 with 7 more fans and temperatures */ + .sensors = SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT | SENSOR_FAN_USB4 | SENSOR_FAN_M2 | + SENSOR_FAN_VRME_HS | SENSOR_FAN_VRMW_HS, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_amd_wrx_90, +}; + static const struct ec_board_info board_info_pro_ws_x570_ace = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CHIPSET | @@ -650,6 +687,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_pro_art_x870E_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR", &board_info_pro_art_b550_creator), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS WRX90E-SAGE SE", + &board_info_pro_ws_wrx90e_sage_se), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", &board_info_pro_ws_x570_ace), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII DARK HERO", @@ -1173,6 +1212,9 @@ static int asus_ec_probe(struct platform_device *pdev) case family_amd_800_series: ec_data->sensors_info = sensors_family_amd_800; break; + case family_amd_wrx_90: + ec_data->sensors_info = sensors_family_amd_wrx_90; + break; case family_intel_300_series: ec_data->sensors_info = sensors_family_intel_300; break; -- cgit v1.2.3 From aa52d636641bc9b38b033ab3dd1c6c4e059a863d Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Sat, 30 Aug 2025 15:12:15 +0200 Subject: hwmon: (asus-ec-sensors) refine config description Remove outdated mention of the supported mmotherboard families and add a hint which sensor readings are available via the module. Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250830131224.748481-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 9d28fcf7cd2a..c53c041c32ef 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2673,9 +2673,10 @@ config SENSORS_ASUS_EC depends on ACPI_EC help If you say yes here you get support for the ACPI embedded controller - hardware monitoring interface found in ASUS motherboards. The driver - currently supports B550/X570 boards, although other ASUS boards might - provide this monitoring interface as well. + hardware monitoring interface found in some ASUS motherboards. This is + where such sensors as water flow and temperature, optional fans, and + additional temperature sensors (T_Sensor, chipset temperatures) + find themselves. This driver can also be built as a module. If so, the module will be called asus_ec_sensors. -- cgit v1.2.3 From 402dfbe7ef6bd5d13341a970c47282c7d60cf9a0 Mon Sep 17 00:00:00 2001 From: Debanil Chowdhury Date: Sun, 31 Aug 2025 04:45:54 +0000 Subject: hwmon: crps: Fix typos in crps.rst documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed a misspelling in crps.rst documentation: "Critial" → "Critical". Found using codespell tool. Signed-off-by: Debanil Chowdhury Link: https://lore.kernel.org/r/20250831045710.6009-1-kerneldev@debanilchowdhury.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/crps.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/crps.rst b/Documentation/hwmon/crps.rst index 87380b496558..d42ea59d2dae 100644 --- a/Documentation/hwmon/crps.rst +++ b/Documentation/hwmon/crps.rst @@ -43,7 +43,7 @@ curr1_label "iin" curr1_input Measured input current curr1_max Maximum input current curr1_max_alarm Input maximum current high alarm -curr1_crit Critial high input current +curr1_crit Critical high input current curr1_crit_alarm Input critical current high alarm curr1_rated_max Maximum rated input current @@ -51,7 +51,7 @@ curr2_label "iout1" curr2_input Measured output current curr2_max Maximum output current curr2_max_alarm Output maximum current high alarm -curr2_crit Critial high output current +curr2_crit Critical high output current curr2_crit_alarm Output critical current high alarm curr2_rated_max Maximum rated output current -- cgit v1.2.3 From fa1ab48bfe97c86278ddf7fb535bc597e64fd210 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Mon, 25 Aug 2025 21:02:43 +0300 Subject: dt-bindings: hwmon: tmp102: Add label property Add support for an optional label property similar to other hwmon devices. This allows, in case of boards with multiple TMP102 sensors, to assign distinct names to each instance. Signed-off-by: Flaviu Nistor Reviewed-by: Rob Herring (Arm) Link: https://lore.kernel.org/r/20250825180248.1943607-1-flaviu.nistor@gmail.com [groeck: Dropped unnecessary "|" after "description:"] Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml index 4c89448eba0d..96b2e4969f78 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,tmp102.yaml @@ -20,6 +20,10 @@ properties: reg: maxItems: 1 + label: + description: + A descriptive name for this channel, like "ambient" or "psu". + "#thermal-sensor-cells": const: 1 @@ -45,6 +49,7 @@ examples: reg = <0x48>; interrupt-parent = <&gpio7>; interrupts = <16 IRQ_TYPE_LEVEL_LOW>; + label = "somelabel"; vcc-supply = <&supply>; #thermal-sensor-cells = <1>; }; -- cgit v1.2.3 From d41f80bd43e6f613078060f63ad60f98e4773b56 Mon Sep 17 00:00:00 2001 From: Flaviu Nistor Date: Mon, 25 Aug 2025 21:02:44 +0300 Subject: hwmon: tmp102: Add support for label Add support for label sysfs attribute similar to other hwmon devices. This is particularly useful for systems with multiple sensors on the same board, where identifying individual sensors is much easier since labels can be defined via device tree. Signed-off-by: Flaviu Nistor Link: https://lore.kernel.org/r/20250825180248.1943607-2-flaviu.nistor@gmail.com [groeck: Fixed continuation line alignment] Signed-off-by: Guenter Roeck --- drivers/hwmon/tmp102.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/tmp102.c b/drivers/hwmon/tmp102.c index a02daa496c9c..376e0eac8cc1 100644 --- a/drivers/hwmon/tmp102.c +++ b/drivers/hwmon/tmp102.c @@ -53,6 +53,7 @@ #define CONVERSION_TIME_MS 35 /* in milli-seconds */ struct tmp102 { + const char *label; struct regmap *regmap; u16 config_orig; unsigned long ready_time; @@ -70,6 +71,16 @@ static inline u16 tmp102_mC_to_reg(int val) return (val * 128) / 1000; } +static int tmp102_read_string(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, const char **str) +{ + struct tmp102 *tmp102 = dev_get_drvdata(dev); + + *str = tmp102->label; + + return 0; +} + static int tmp102_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, long *temp) { @@ -128,12 +139,18 @@ static int tmp102_write(struct device *dev, enum hwmon_sensor_types type, static umode_t tmp102_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { + const struct tmp102 *tmp102 = data; + if (type != hwmon_temp) return 0; switch (attr) { case hwmon_temp_input: return 0444; + case hwmon_temp_label: + if (tmp102->label) + return 0444; + return 0; case hwmon_temp_max_hyst: case hwmon_temp_max: return 0644; @@ -146,12 +163,13 @@ static const struct hwmon_channel_info * const tmp102_info[] = { HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), HWMON_CHANNEL_INFO(temp, - HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_HYST), + HWMON_T_INPUT | HWMON_T_LABEL | HWMON_T_MAX | HWMON_T_MAX_HYST), NULL }; static const struct hwmon_ops tmp102_hwmon_ops = { .is_visible = tmp102_is_visible, + .read_string = tmp102_read_string, .read = tmp102_read, .write = tmp102_write, }; @@ -213,6 +231,8 @@ static int tmp102_probe(struct i2c_client *client) if (!tmp102) return -ENOMEM; + of_property_read_string(dev->of_node, "label", &tmp102->label); + i2c_set_clientdata(client, tmp102); tmp102->regmap = devm_regmap_init_i2c(client, &tmp102_regmap_config); -- cgit v1.2.3 From 58639dfde0c29434e902a0efe213b35ede35604e Mon Sep 17 00:00:00 2001 From: Chris Packham Date: Fri, 29 Aug 2025 15:05:09 +1200 Subject: dt-bindings: hwmon: ti,ina2xx: Add INA780 device Add a compatible string for the INA780 device. Signed-off-by: Chris Packham Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250829030512.1179998-2-chris.packham@alliedtelesis.co.nz Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index fa68b99ef2e2..980ecba6d811 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -32,6 +32,7 @@ properties: - ti,ina237 - ti,ina238 - ti,ina260 + - ti,ina780 reg: maxItems: 1 -- cgit v1.2.3 From f19617d1478aa595b5e2439847d34f3ac414c836 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 1 Sep 2025 08:59:02 -0700 Subject: dt-bindings: hwmon: ti,ina2xx: Update details for various chips ti,maximum-expected-current-microamp, ti,shunt-gain, and shunt-resistor properties are not supported on all chips described in this bindings file. Update the bindings accordingly. Cc: Chris Packham Signed-off-by: Guenter Roeck Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250901155902.2667063-1-linux@roeck-us.net Signed-off-by: Guenter Roeck --- .../devicetree/bindings/hwmon/ti,ina2xx.yaml | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 980ecba6d811..8b491be9c49d 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -115,10 +115,39 @@ allOf: - ti,ina237 - ti,ina238 - ti,ina260 + - ti,ina780 then: properties: ti,maximum-expected-current-microamp: false + - if: + properties: + compatible: + contains: + enum: + - silergy,sy24655 + - ti,ina209 + - ti,ina219 + - ti,ina220 + - ti,ina226 + - ti,ina230 + - ti,ina231 + - ti,ina260 + - ti,ina780 + then: + properties: + ti,shunt-gain: false + + - if: + properties: + compatible: + contains: + enum: + - ti,ina780 + then: + properties: + shunt-resistor: false + unevaluatedProperties: false examples: -- cgit v1.2.3 From 34c61c198d06b83c1a9da88005e3f4eec85447da Mon Sep 17 00:00:00 2001 From: Tom Ingleby Date: Tue, 2 Sep 2025 20:17:56 -0700 Subject: hwmon: (asus-ec-sensors) add ROG STRIX Z690-E GAMING WIFI Add support for the ASUS ROG STRIX Z690-E GAMING WIFI Signed-off-by: Tom Ingleby Link: https://lore.kernel.org/r/20250903031800.4173-1-tom@ewsting.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 12c6b5b266bb..e5052159ffa0 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -36,6 +36,7 @@ Supported boards: * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 + * ROG STRIX Z690-E GAMING WIFI * ROG STRIX Z790-E GAMING WIFI II * ROG STRIX Z790-I GAMING WIFI * ROG ZENITH II EXTREME diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 09a751b44ee2..728e21fddae8 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -625,6 +625,12 @@ static const struct ec_board_info board_info_strix_z690_a_gaming_wifi_d4 = { .family = family_intel_600_series, }; +static const struct ec_board_info board_info_strix_z690_e_gaming_wifi = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_intel_600_series, +}; + static const struct ec_board_info board_info_strix_z790_e_gaming_wifi_ii = { .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_FAN_CPU_OPT, @@ -735,6 +741,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_z490_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-A GAMING WIFI D4", &board_info_strix_z690_a_gaming_wifi_d4), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z690-E GAMING WIFI", + &board_info_strix_z690_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-E GAMING WIFI II", &board_info_strix_z790_e_gaming_wifi_ii), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z790-I GAMING WIFI", -- cgit v1.2.3 From 5529bc1a2ff047ca94a64d91ec3ed52b01946837 Mon Sep 17 00:00:00 2001 From: Michael Tandy Date: Wed, 3 Sep 2025 20:47:36 +0200 Subject: hwmon: (asus-ec-sensors) sort declarations Sort all the declarations in the source file. Contributors are asked to insert new entries keeping alphabetical order, but the existing ones were not completely sorted. Signed-off-by: Michael Tandy Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250903184753.5876-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 170 ++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 728e21fddae8..ecb058e8755b 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -384,6 +384,52 @@ struct ec_board_info { enum board_family family; }; +static const struct ec_board_info board_info_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, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, +}; + +static const struct ec_board_info board_info_crosshair_viii_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_CHIPSET | + SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | + SENSOR_IN_CPU_CORE, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, +}; + +static const struct ec_board_info board_info_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, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, +}; + +static const struct ec_board_info board_info_crosshair_x670e_gene = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_T_SENSOR | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_600_series, +}; + +static const struct ec_board_info board_info_crosshair_x670e_hero = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_SET_TEMP_WATER, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_maximus_vi_hero = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -393,6 +439,22 @@ static const struct ec_board_info board_info_maximus_vi_hero = { .family = family_intel_300_series, }; +static const struct ec_board_info board_info_maximus_xi_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, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_intel_300_series, +}; + +static const struct ec_board_info board_info_maximus_z690_formula = { + .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | + SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW, + .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, + .family = family_intel_600_series, +}; + static const struct ec_board_info board_info_prime_x470_pro = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -417,6 +479,14 @@ static const struct ec_board_info board_info_prime_x670e_pro_wifi = { .family = family_amd_600_series, }; +static const struct ec_board_info board_info_pro_art_b550_creator = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, + .family = family_amd_500_series, +}; + static const struct ec_board_info board_info_pro_art_x570_creator_wifi = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT | @@ -441,14 +511,6 @@ static const struct ec_board_info board_info_pro_art_x870E_creator_wifi = { .family = family_amd_800_series, }; -static const struct ec_board_info board_info_pro_art_b550_creator = { - .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | - SENSOR_TEMP_T_SENSOR | - SENSOR_FAN_CPU_OPT, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_amd_500_series, -}; - static const struct ec_board_info board_info_pro_ws_wrx90e_sage_se = { /* Board also has a nct6798 with 7 more fans and temperatures */ .sensors = SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_T_SENSOR | @@ -466,68 +528,6 @@ static const struct ec_board_info board_info_pro_ws_x570_ace = { .family = family_amd_500_series, }; -static const struct ec_board_info board_info_crosshair_x670e_hero = { - .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | - SENSOR_TEMP_MB | SENSOR_TEMP_VRM | - SENSOR_SET_TEMP_WATER, - .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, - .family = family_amd_600_series, -}; - -static const struct ec_board_info board_info_crosshair_x670e_gene = { - .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | - SENSOR_TEMP_T_SENSOR | - SENSOR_TEMP_MB | SENSOR_TEMP_VRM, - .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, - .family = family_amd_600_series, -}; - -static const struct ec_board_info board_info_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, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_amd_500_series, -}; - -static const struct ec_board_info board_info_crosshair_viii_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_CHIPSET | - SENSOR_FAN_WATER_FLOW | SENSOR_CURR_CPU | - SENSOR_IN_CPU_CORE, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_amd_500_series, -}; - -static const struct ec_board_info board_info_maximus_xi_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, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_intel_300_series, -}; - -static const struct ec_board_info board_info_maximus_z690_formula = { - .sensors = SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | - SENSOR_SET_TEMP_WATER | SENSOR_FAN_WATER_FLOW, - .mutex_path = ASUS_HW_ACCESS_MUTEX_RMTW_ASMX, - .family = family_intel_600_series, -}; - -static const struct ec_board_info board_info_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, - .mutex_path = ASUS_HW_ACCESS_MUTEX_ASMX, - .family = family_amd_500_series, -}; - static const struct ec_board_info board_info_strix_b550_e_gaming = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | @@ -645,6 +645,15 @@ static const struct ec_board_info board_info_strix_z790_i_gaming_wifi = { .family = family_intel_700_series, }; +static const struct ec_board_info board_info_tuf_gaming_x670e_plus = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | + SENSOR_FAN_CPU_OPT, + .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_zenith_ii_extreme = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | SENSOR_TEMP_VRM | SENSOR_SET_TEMP_WATER | @@ -657,15 +666,6 @@ static const struct ec_board_info board_info_zenith_ii_extreme = { .family = family_amd_500_series, }; -static const struct ec_board_info board_info_tuf_gaming_x670e_plus = { - .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | - SENSOR_TEMP_MB | SENSOR_TEMP_VRM | - SENSOR_TEMP_WATER_IN | SENSOR_TEMP_WATER_OUT | - SENSOR_FAN_CPU_OPT, - .mutex_path = ACPI_GLOBAL_LOCK_PSEUDO_PATH, - .family = family_amd_600_series, -}; - #define DMI_EXACT_MATCH_ASUS_BOARD_NAME(name, board_info) \ { \ .matches = { \ @@ -685,14 +685,14 @@ static const struct dmi_system_id dmi_table[] = { &board_info_prime_x570_pro), DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X670E-PRO WIFI", &board_info_prime_x670e_pro_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR", + &board_info_pro_art_b550_creator), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI", &board_info_pro_art_x570_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X670E-CREATOR WIFI", &board_info_pro_art_x670E_creator_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X870E-CREATOR WIFI", &board_info_pro_art_x870E_creator_wifi), - DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR", - &board_info_pro_art_b550_creator), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS WRX90E-SAGE SE", &board_info_pro_ws_wrx90e_sage_se), DMI_EXACT_MATCH_ASUS_BOARD_NAME("Pro WS X570-ACE", @@ -705,18 +705,18 @@ static const struct dmi_system_id dmi_table[] = { &board_info_crosshair_viii_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII HERO (WI-FI)", &board_info_crosshair_viii_hero), - DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", - &board_info_crosshair_x670e_hero), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", + &board_info_crosshair_viii_impact), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E GENE", &board_info_crosshair_x670e_gene), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR X670E HERO", + &board_info_crosshair_x670e_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO", &board_info_maximus_xi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS XI HERO (WI-FI)", &board_info_maximus_xi_hero), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG MAXIMUS Z690 FORMULA", &board_info_maximus_z690_formula), - DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG CROSSHAIR VIII IMPACT", - &board_info_crosshair_viii_impact), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-E GAMING", &board_info_strix_b550_e_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX B550-I GAMING", -- cgit v1.2.3 From 8702c8f53d93929a804cd16015e229cb0770b4ec Mon Sep 17 00:00:00 2001 From: Eugene Shalygin Date: Wed, 3 Sep 2025 21:17:26 +0200 Subject: hwmon: (asus-ec-sensors) add PRIME Z270-A MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for the PRIME Z270-A board. Tested-by: Jan Philipp Groß Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250903191736.14451-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index e5052159ffa0..baf9eba5957c 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -8,6 +8,7 @@ Supported boards: * PRIME X470-PRO * PRIME X570-PRO * PRIME X670E-PRO WIFI + * PRIME Z270-A * Pro WS X570-ACE * Pro WS WRX90E-SAGE SE * ProArt X570-CREATOR WIFI diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index ecb058e8755b..f580ff2e212f 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -60,6 +60,8 @@ static char *mutex_path_override; #define ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0 "\\_SB_.PCI0.SBRG.SIO1.MUT0" +#define ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0 "\\_SB_.PCI0.LPCB.SIO1.MUT0" + #define MAX_IDENTICAL_BOARD_VARIATIONS 3 /* Moniker for the ACPI global lock (':' is not allowed in ASL identifiers) */ @@ -181,6 +183,7 @@ enum board_family { family_amd_600_series, family_amd_800_series, family_amd_wrx_90, + family_intel_200_series, family_intel_300_series, family_intel_400_series, family_intel_600_series, @@ -306,6 +309,18 @@ static const struct ec_sensor_info sensors_family_amd_wrx_90[] = { EC_SENSOR("T_Sensor", hwmon_temp, 1, 0x01, 0x04), }; +static const struct ec_sensor_info sensors_family_intel_200[] = { + [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_fan_cpu_opt] = + EC_SENSOR("CPU_Opt", hwmon_fan, 2, 0x00, 0xbc), +}; + static const struct ec_sensor_info sensors_family_intel_300[] = { [ec_sensor_temp_chipset] = EC_SENSOR("Chipset", hwmon_temp, 1, 0x00, 0x3a), @@ -479,6 +494,13 @@ static const struct ec_board_info board_info_prime_x670e_pro_wifi = { .family = family_amd_600_series, }; +static const struct ec_board_info board_info_prime_z270_a = { + .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | + SENSOR_TEMP_T_SENSOR | SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_LPCB_SIO1_MUT0, + .family = family_intel_200_series, +}; + static const struct ec_board_info board_info_pro_art_b550_creator = { .sensors = SENSOR_SET_TEMP_CHIPSET_CPU_MB | SENSOR_TEMP_T_SENSOR | @@ -685,6 +707,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_prime_x570_pro), DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME X670E-PRO WIFI", &board_info_prime_x670e_pro_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("PRIME Z270-A", + &board_info_prime_z270_a), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt B550-CREATOR", &board_info_pro_art_b550_creator), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ProArt X570-CREATOR WIFI", @@ -1223,6 +1247,9 @@ static int asus_ec_probe(struct platform_device *pdev) case family_amd_wrx_90: ec_data->sensors_info = sensors_family_amd_wrx_90; break; + case family_intel_200_series: + ec_data->sensors_info = sensors_family_intel_200; + break; case family_intel_300_series: ec_data->sensors_info = sensors_family_intel_300; break; -- cgit v1.2.3 From bd48b5a4e8d3e1ac2c1ba1e18ba1350ea2c9eb10 Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 4 Sep 2025 22:21:09 +0200 Subject: dt-bindings: hwmon: pwm-fan: Document after shutdown fan settings Document fan-shutdown-percent property, used to describe fan RPM in percent set during shutdown. This is used to keep the fan running at fixed RPM after the kernel shut down, which is useful on hardware that does keep heating itself even after the kernel did shut down, for example from some sort of management core. Signed-off-by: Marek Vasut Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250904202157.170600-1-marek.vasut+renesas@mailbox.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/pwm-fan.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml index 8b4ed5ee962f..a84cc3a4cfdc 100644 --- a/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml +++ b/Documentation/devicetree/bindings/hwmon/pwm-fan.yaml @@ -31,6 +31,15 @@ properties: it must be self resetting edge interrupts. maxItems: 1 + fan-shutdown-percent: + description: + Fan RPM in percent set during shutdown. This is used to keep the fan + running at fixed RPM after the kernel shut down, which is useful on + hardware that does keep heating itself even after the kernel did shut + down, for example from some sort of management core. + minimum: 0 + maximum: 100 + fan-stop-to-start-percent: description: Minimum fan RPM in percent to start when stopped. -- cgit v1.2.3 From da0a3cc73a2bffe8faef6227a74e5a2de2e79a0c Mon Sep 17 00:00:00 2001 From: Marek Vasut Date: Thu, 4 Sep 2025 22:21:10 +0200 Subject: hwmon: (pwm-fan) Implement after shutdown fan settings Add fan-shutdown-percent property, used to describe fan RPM in percent set during shutdown. This is used to keep the fan running at fixed RPM after the kernel shut down, which is useful on hardware that does keep heating itself even after the kernel did shut down, for example from some sort of management core. The current behavior of pwm-fan is to unconditionally stop the fan on shutdown, which is not always the safe and correct thing to do, so let the hardware description include the expected behavior. Signed-off-by: Marek Vasut Tested-by: Wolfram Sang Link: https://lore.kernel.org/r/20250904202157.170600-2-marek.vasut+renesas@mailbox.org Signed-off-by: Guenter Roeck --- drivers/hwmon/pwm-fan.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/pwm-fan.c b/drivers/hwmon/pwm-fan.c index d0fe53451bdf..37269db2de84 100644 --- a/drivers/hwmon/pwm-fan.c +++ b/drivers/hwmon/pwm-fan.c @@ -64,6 +64,7 @@ struct pwm_fan_ctx { u64 pwm_duty_cycle_from_stopped; u32 pwm_usec_from_stopped; + u8 pwm_shutdown; }; /* This handler assumes self resetting edge triggered interrupt. */ @@ -484,9 +485,14 @@ static void pwm_fan_cleanup(void *__ctx) struct pwm_fan_ctx *ctx = __ctx; timer_delete_sync(&ctx->rpm_timer); - /* Switch off everything */ - ctx->enable_mode = pwm_disable_reg_disable; - pwm_fan_power_off(ctx, true); + if (ctx->pwm_shutdown) { + ctx->enable_mode = pwm_enable_reg_enable; + __set_pwm(ctx, ctx->pwm_shutdown); + } else { + /* Switch off everything */ + ctx->enable_mode = pwm_disable_reg_disable; + pwm_fan_power_off(ctx, true); + } } static int pwm_fan_probe(struct platform_device *pdev) @@ -498,6 +504,7 @@ static int pwm_fan_probe(struct platform_device *pdev) int ret; const struct hwmon_channel_info **channels; u32 initial_pwm, pwm_min_from_stopped = 0; + u32 pwm_shutdown_percent = 0; u32 *fan_channel_config; int channel_count = 1; /* We always have a PWM channel. */ int i; @@ -648,6 +655,11 @@ static int pwm_fan_probe(struct platform_device *pdev) channels[1] = &ctx->fan_channel; } + ret = device_property_read_u32(dev, "fan-shutdown-percent", + &pwm_shutdown_percent); + if (!ret && pwm_shutdown_percent) + ctx->pwm_shutdown = (clamp(pwm_shutdown_percent, 0, 100) * 255) / 100; + ret = device_property_read_u32(dev, "fan-stop-to-start-percent", &pwm_min_from_stopped); if (!ret && pwm_min_from_stopped) { -- cgit v1.2.3 From 0bcd01f757bc06471c82a137eafee281ef1b6e38 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 28 Aug 2024 21:56:57 -0700 Subject: hwmon: Introduce 64-bit energy attribute support Many chips require 64-bit variables to display the accumulated energy, even more so since the energy units are micro-Joule. Add new sensor type "energy64" to support reporting the chip energy as 64-bit values. Changing the entire hardware monitoring API is not feasible, and it is only really necessary to support reading 64-bit values for the "energyX_input" attribute. For this reason, keep the API as-is and use type casts on both ends to pass 64-bit pointers when reading the accumulated energy. On the write side (which is only useful for the energyX_enable attribute), keep passing the written value as long. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 3 +++ drivers/hwmon/hwmon.c | 14 ++++++++++---- include/linux/hwmon.h | 1 + include/trace/events/hwmon.h | 10 +++++----- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index e47fc757e63e..037b69c23cb5 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -159,6 +159,7 @@ It contains following fields: hwmon_curr Current sensor hwmon_power Power sensor hwmon_energy Energy sensor + hwmon_energy64 Energy sensor, reported as 64-bit signed value hwmon_humidity Humidity sensor hwmon_fan Fan speed sensor hwmon_pwm PWM control @@ -288,6 +289,8 @@ Parameters: The sensor channel number. val: Pointer to attribute value. + For hwmon_energy64, `'val`' is passed as `long *` but needs + a typecast to `s64 *`. Return value: 0 on success, a negative error number otherwise. diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 1688c210888a..2e17f3a4c59b 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -426,18 +426,22 @@ static ssize_t hwmon_attr_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + s64 val64; long val; int ret; ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index, - &val); + (hattr->type == hwmon_energy64) ? (long *)&val64 : &val); if (ret < 0) return ret; + if (hattr->type != hwmon_energy64) + val64 = val; + trace_hwmon_attr_show(hattr->index + hwmon_attr_base(hattr->type), - hattr->name, val); + hattr->name, val64); - return sprintf(buf, "%ld\n", val); + return sprintf(buf, "%lld\n", val64); } static ssize_t hwmon_attr_show_string(struct device *dev, @@ -478,7 +482,7 @@ static ssize_t hwmon_attr_store(struct device *dev, return ret; trace_hwmon_attr_store(hattr->index + hwmon_attr_base(hattr->type), - hattr->name, val); + hattr->name, (s64)val); return count; } @@ -734,6 +738,7 @@ static const char * const *__templates[] = { [hwmon_curr] = hwmon_curr_attr_templates, [hwmon_power] = hwmon_power_attr_templates, [hwmon_energy] = hwmon_energy_attr_templates, + [hwmon_energy64] = hwmon_energy_attr_templates, [hwmon_humidity] = hwmon_humidity_attr_templates, [hwmon_fan] = hwmon_fan_attr_templates, [hwmon_pwm] = hwmon_pwm_attr_templates, @@ -747,6 +752,7 @@ static const int __templates_size[] = { [hwmon_curr] = ARRAY_SIZE(hwmon_curr_attr_templates), [hwmon_power] = ARRAY_SIZE(hwmon_power_attr_templates), [hwmon_energy] = ARRAY_SIZE(hwmon_energy_attr_templates), + [hwmon_energy64] = ARRAY_SIZE(hwmon_energy_attr_templates), [hwmon_humidity] = ARRAY_SIZE(hwmon_humidity_attr_templates), [hwmon_fan] = ARRAY_SIZE(hwmon_fan_attr_templates), [hwmon_pwm] = ARRAY_SIZE(hwmon_pwm_attr_templates), diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 3a63dff62d03..886fc90b2d25 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -24,6 +24,7 @@ enum hwmon_sensor_types { hwmon_curr, hwmon_power, hwmon_energy, + hwmon_energy64, hwmon_humidity, hwmon_fan, hwmon_pwm, diff --git a/include/trace/events/hwmon.h b/include/trace/events/hwmon.h index d1ff560cd9b5..3865098f21f1 100644 --- a/include/trace/events/hwmon.h +++ b/include/trace/events/hwmon.h @@ -9,14 +9,14 @@ DECLARE_EVENT_CLASS(hwmon_attr_class, - TP_PROTO(int index, const char *attr_name, long val), + TP_PROTO(int index, const char *attr_name, long long val), TP_ARGS(index, attr_name, val), TP_STRUCT__entry( __field(int, index) __string(attr_name, attr_name) - __field(long, val) + __field(long long, val) ), TP_fast_assign( @@ -25,20 +25,20 @@ DECLARE_EVENT_CLASS(hwmon_attr_class, __entry->val = val; ), - TP_printk("index=%d, attr_name=%s, val=%ld", + TP_printk("index=%d, attr_name=%s, val=%lld", __entry->index, __get_str(attr_name), __entry->val) ); DEFINE_EVENT(hwmon_attr_class, hwmon_attr_show, - TP_PROTO(int index, const char *attr_name, long val), + TP_PROTO(int index, const char *attr_name, long long val), TP_ARGS(index, attr_name, val) ); DEFINE_EVENT(hwmon_attr_class, hwmon_attr_store, - TP_PROTO(int index, const char *attr_name, long val), + TP_PROTO(int index, const char *attr_name, long long val), TP_ARGS(index, attr_name, val) ); -- cgit v1.2.3 From 9a8113a5c6eb23aaaafa9bde043b76a22a3a4e44 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 14:53:58 -0700 Subject: hwmon: (ina238) Drop platform data support There are no in-tree users of ina2xx platform data. Drop support for it. The driver already supports device properties which can be used as alternative if needed. Also remove reference to the non-existing shunt_resistor sysfs attribute from the driver documentation. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 8 ++++---- drivers/hwmon/ina238.c | 8 ++------ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index 9a24da4786a4..9b830e37c986 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -29,10 +29,10 @@ The INA238 is a current shunt, power and temperature monitor with an I2C interface. It includes a number of programmable functions including alerts, conversion rate, sample averaging and selectable shunt voltage accuracy. -The shunt value in micro-ohms can be set via platform data or device tree at -compile-time or via the shunt_resistor attribute in sysfs at run-time. Please -refer to the Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings -if the device tree is used. +The shunt value in micro-ohms can be set via device properties, either from +platform code or from device tree data. Please refer to +Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings if +device tree is used. Sysfs entries ------------- diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 59a2c8889fa2..22e2b862fb33 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -16,8 +16,6 @@ #include #include -#include - /* INA238 register definitions */ #define INA238_CONFIG 0x0 #define INA238_ADC_CONFIG 0x1 @@ -745,7 +743,6 @@ ATTRIBUTE_GROUPS(ina238); static int ina238_probe(struct i2c_client *client) { - struct ina2xx_platform_data *pdata = dev_get_platdata(&client->dev); struct device *dev = &client->dev; struct device *hwmon_dev; struct ina238_data *data; @@ -772,9 +769,8 @@ static int ina238_probe(struct i2c_client *client) } /* load shunt value */ - data->rshunt = INA238_RSHUNT_DEFAULT; - if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0 && pdata) - data->rshunt = pdata->shunt_uohms; + if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0) + data->rshunt = INA238_RSHUNT_DEFAULT; if (data->rshunt == 0) { dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); return -EINVAL; -- cgit v1.2.3 From f2711a19651f28b426cf78792822a37f7641f43c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Fri, 5 Sep 2025 13:20:04 -0700 Subject: hwmon: (ina238) Update documentation and Kconfig entry Update driver documentation and Kconfig entry to list all chips supported by the driver. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 39 ++++++++++++++++++++++++++++++--------- drivers/hwmon/Kconfig | 9 +++++---- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index 9b830e37c986..1ac4e2c419ac 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -5,6 +5,24 @@ Kernel driver ina238 Supported chips: + * Texas Instruments INA228 + + Prefix: 'ina228' + + Addresses: I2C 0x40 - 0x4f + + Datasheet: + https://www.ti.com/lit/gpn/ina228 + + * Texas Instruments INA237 + + Prefix: 'ina237' + + Addresses: I2C 0x40 - 0x4f + + Datasheet: + https://www.ti.com/lit/gpn/ina237 + * Texas Instruments INA238 Prefix: 'ina238' @@ -34,6 +52,13 @@ platform code or from device tree data. Please refer to Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml for bindings if device tree is used. +INA237 is a functionally equivalent variant of INA238 with slightly +different accuracy. INA228 is another variant of INA238 with higher ADC +resolution. This chip also reports the energy. + +SQ52206 is a mostly compatible chip from Sylergy. It reports the energy +as well as the peak power consumption. + Sysfs entries ------------- @@ -53,19 +78,15 @@ in1_max_alarm Maximum shunt voltage alarm power1_input Power measurement (uW) power1_max Maximum power threshold (uW) power1_max_alarm Maximum power alarm +power1_input_highest Peak Power (uW) + (SQ52206 only) curr1_input Current measurement (mA) +energy1_input Energy measurement (uJ) + (SQ52206 and INA237 only) + temp1_input Die temperature measurement (mC) temp1_max Maximum die temperature threshold (mC) temp1_max_alarm Maximum die temperature alarm ======================= ======================================================= - -Additional sysfs entries for sq52206 ------------------------------------- - -======================= ======================================================= -energy1_input Energy measurement (uJ) - -power1_input_highest Peak Power (uW) -======================= ======================================================= diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c53c041c32ef..6bdce991f5f0 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2252,13 +2252,14 @@ config SENSORS_INA2XX will be called ina2xx. config SENSORS_INA238 - tristate "Texas Instruments INA238" + tristate "Texas Instruments INA238 and compatibles" depends on I2C select REGMAP_I2C help - If you say yes here you get support for the INA238 power monitor - chip. This driver supports voltage, current, power and temperature - measurements as well as alarm configuration. + If you say yes here you get support for INA228, INA237, INA238, and + SQ52206 power monitor chips. This driver supports voltage, current, + power, energy, and temperature measurements as well as alarm + configuration. This driver can also be built as a module. If so, the module will be called ina238. -- cgit v1.2.3 From 8640f9ab1015741e22dff2dd1d6665ad024d5534 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 1 Sep 2025 11:01:53 -0700 Subject: hwmon: (ina238) Drop pointless power attribute check on attribute writes There is only a single writeable power attribute, and it is very unlikely that the chips supported by this driver will ever require another one. That means checking for that attribute during runtime is unnecessary. Drop the check. Rename the write function from ina238_write_power() to ina238_write_power_max() to reflect that a single attribute is written. No functional change intended. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 22e2b862fb33..23195dead74f 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -503,14 +503,11 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) return 0; } -static int ina238_write_power(struct device *dev, u32 attr, long val) +static int ina238_write_power_max(struct device *dev, long val) { struct ina238_data *data = dev_get_drvdata(dev); long regval; - if (attr != hwmon_power_max) - return -EOPNOTSUPP; - /* * Unsigned postive values. Compared against the 24-bit power register, * lower 8-bits are truncated. Same conversion to/from uW as POWER @@ -628,7 +625,7 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type, err = ina238_write_in(dev, attr, channel, val); break; case hwmon_power: - err = ina238_write_power(dev, attr, val); + err = ina238_write_power_max(dev, val); break; case hwmon_temp: err = ina238_write_temp(dev, attr, val); -- cgit v1.2.3 From 40a5da1ec101476dd6abb9cfa181f1b50dc24ad0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 30 Aug 2025 17:38:51 -0700 Subject: hwmon: (ina238) Rework and simplify temperature calculations The temperature register is 16 bit wide for all chips. The decimal point is at the same location (bit 7 = 1 degree C). That means we can use the resolution to calculate temperatures. Do that to simplify the code. There is only a single writeable temperature attribute, and it is very unlikely that the chips supported by this driver will ever require another one. That means checking for that attribute in the write function is unnecessary. Drop the check. Rename the write function from ina238_write_temp() to ina238_write_temp_max() to reflect that a single attribute is written. Also extend the accepted temperature value range to the range supported by the chip registers. Limiting the accepted value range to the temperature range supported by the chip would make it impossible to read an out-of-range limit from the chip and to write the same value back into it. This is undesirable, especially since the maximum temperature register does contain the maximum register value after a chip reset, not the temperature limit supported by the chip. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 52 +++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 23195dead74f..e386a0f83fbb 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -103,10 +103,7 @@ #define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */ #define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */ -#define INA238_DIE_TEMP_LSB 1250000 /* 125.0000 mC/lsb */ #define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */ -#define SQ52206_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */ -#define INA228_DIE_TEMP_LSB 78125 /* 7.8125 mC/lsb */ static const struct regmap_config ina238_regmap_config = { .max_register = INA238_REGISTERS, @@ -120,11 +117,10 @@ struct ina238_config { bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */ bool has_power_highest; /* chip detection power peak */ bool has_energy; /* chip detection energy */ - u8 temp_shift; /* fixed parameters for temp calculate */ + u8 temp_resolution; /* temperature register resolution in bit */ u32 power_calculate_factor; /* fixed parameters for power calculate */ u16 config_default; /* Power-on default state */ int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */ - int temp_lsb; /* use for temperature calculate */ }; struct ina238_data { @@ -141,41 +137,37 @@ static const struct ina238_config ina238_config[] = { .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, - .temp_shift = 4, .power_calculate_factor = 20, .config_default = INA238_CONFIG_DEFAULT, .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, - .temp_lsb = INA238_DIE_TEMP_LSB, + .temp_resolution = 12, }, [ina237] = { .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, - .temp_shift = 4, .power_calculate_factor = 20, .config_default = INA238_CONFIG_DEFAULT, .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, - .temp_lsb = INA238_DIE_TEMP_LSB, + .temp_resolution = 12, }, [sq52206] = { .has_20bit_voltage_current = false, .has_energy = true, .has_power_highest = true, - .temp_shift = 0, .power_calculate_factor = 24, .config_default = SQ52206_CONFIG_DEFAULT, .bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB, - .temp_lsb = SQ52206_DIE_TEMP_LSB, + .temp_resolution = 16, }, [ina228] = { .has_20bit_voltage_current = true, .has_energy = true, .has_power_highest = false, - .temp_shift = 0, .power_calculate_factor = 20, .config_default = INA238_CONFIG_DEFAULT, .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, - .temp_lsb = INA228_DIE_TEMP_LSB, + .temp_resolution = 16, }, }; @@ -522,6 +514,11 @@ static int ina238_write_power_max(struct device *dev, long val) return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); } +static int ina238_temp_from_reg(s16 regval, u8 resolution) +{ + return ((regval >> (16 - resolution)) * 1000) >> (resolution - 9); +} + static int ina238_read_temp(struct device *dev, u32 attr, long *val) { struct ina238_data *data = dev_get_drvdata(dev); @@ -533,17 +530,14 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val) err = regmap_read(data->regmap, INA238_DIE_TEMP, ®val); if (err) return err; - /* Signed, result in mC */ - *val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) * - (s64)data->config->temp_lsb, 10000); + *val = ina238_temp_from_reg(regval, data->config->temp_resolution); break; case hwmon_temp_max: err = regmap_read(data->regmap, INA238_TEMP_LIMIT, ®val); if (err) return err; /* Signed, result in mC */ - *val = div_s64(((s64)((s16)regval) >> data->config->temp_shift) * - (s64)data->config->temp_lsb, 10000); + *val = ina238_temp_from_reg(regval, data->config->temp_resolution); break; case hwmon_temp_max_alarm: err = regmap_read(data->regmap, INA238_DIAG_ALERT, ®val); @@ -559,19 +553,21 @@ static int ina238_read_temp(struct device *dev, u32 attr, long *val) return 0; } -static int ina238_write_temp(struct device *dev, u32 attr, long val) +static u16 ina238_temp_to_reg(long val, u8 resolution) { - struct ina238_data *data = dev_get_drvdata(dev); - int regval; + int fraction = 1000 - DIV_ROUND_CLOSEST(1000, BIT(resolution - 9)); - if (attr != hwmon_temp_max) - return -EOPNOTSUPP; + val = clamp_val(val, -255000 - fraction, 255000 + fraction); + + return (DIV_ROUND_CLOSEST(val << (resolution - 9), 1000) << (16 - resolution)) & 0xffff; +} - /* Signed */ - val = clamp_val(val, -40000, 125000); - regval = div_s64(val * 10000, data->config->temp_lsb) << data->config->temp_shift; - regval = clamp_val(regval, S16_MIN, S16_MAX) & (0xffff << data->config->temp_shift); +static int ina238_write_temp_max(struct device *dev, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + regval = ina238_temp_to_reg(val, data->config->temp_resolution); return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); } @@ -628,7 +624,7 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type, err = ina238_write_power_max(dev, val); break; case hwmon_temp: - err = ina238_write_temp(dev, attr, val); + err = ina238_write_temp_max(dev, val); break; default: err = -EOPNOTSUPP; -- cgit v1.2.3 From 4a4fcd611295af96af51574b31f9e19e7505f965 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 28 Aug 2025 17:44:17 -0700 Subject: hwmon: (ina238) Pre-calculate current, power, and energy LSB Current, power, and energy LSB do not change during runtime, so we can pre-calculate the respective values. The power LSB can be derived from the current LSB using the equation in the datasheets. Similar, the energy LSB can be derived from the power LSB. Also add support for chips with built-in shunt resistor by providing a chip specific configuration parameter for the current LSB. The relationship of current -> power -> energy LSB values in those chips is the same as in chips with external shunt resistor, so configuration parameters for power and energy LSB are not needed. Use ROUND_CLOSEST functions instead of divide operations to reduce rounding errors. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index e386a0f83fbb..316a7dc720f5 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -118,9 +118,10 @@ struct ina238_config { bool has_power_highest; /* chip detection power peak */ bool has_energy; /* chip detection energy */ u8 temp_resolution; /* temperature register resolution in bit */ - u32 power_calculate_factor; /* fixed parameters for power calculate */ + u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */ u16 config_default; /* Power-on default state */ int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */ + int current_lsb; /* current LSB, in uA */ }; struct ina238_data { @@ -130,6 +131,9 @@ struct ina238_data { struct regmap *regmap; u32 rshunt; int gain; + int current_lsb; /* current LSB, in uA */ + int power_lsb; /* power LSB, in uW */ + int energy_lsb; /* energy LSB, in uJ */ }; static const struct ina238_config ina238_config[] = { @@ -422,9 +426,8 @@ static int ina238_read_current(struct device *dev, u32 attr, long *val) regval = (s16)regval; } - /* Signed register, fixed 1mA current lsb. result in mA */ - *val = div_s64((s64)regval * INA238_FIXED_SHUNT * data->gain, - data->rshunt * 4); + /* Signed register. Result in mA */ + *val = DIV_S64_ROUND_CLOSEST((s64)regval * data->current_lsb, 1000); /* Account for 4 bit offset */ if (data->config->has_20bit_voltage_current) @@ -450,9 +453,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) if (err) return err; - /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ - power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain * - data->config->power_calculate_factor, 4 * 100 * data->rshunt); + power = (long long)regval * data->power_lsb; /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -461,9 +462,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) if (err) return err; - /* Fixed 1mA lsb, scaled by 1000000 to have result in uW */ - power = div_u64(regval * 1000ULL * INA238_FIXED_SHUNT * data->gain * - data->config->power_calculate_factor, 4 * 100 * data->rshunt); + power = (long long)regval * data->power_lsb; /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -476,8 +475,7 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) * Truncated 24-bit compare register, lower 8-bits are * truncated. Same conversion to/from uW as POWER register. */ - power = div_u64((regval << 8) * 1000ULL * INA238_FIXED_SHUNT * data->gain * - data->config->power_calculate_factor, 4 * 100 * data->rshunt); + power = ((long long)regval << 8) * data->power_lsb; /* Clamp value to maximum value of long */ *val = clamp_val(power, 0, LONG_MAX); break; @@ -498,7 +496,6 @@ static int ina238_read_power(struct device *dev, u32 attr, long *val) static int ina238_write_power_max(struct device *dev, long val) { struct ina238_data *data = dev_get_drvdata(dev); - long regval; /* * Unsigned postive values. Compared against the 24-bit power register, @@ -506,12 +503,11 @@ static int ina238_write_power_max(struct device *dev, long val) * register. * The first clamp_val() is to establish a baseline to avoid overflows. */ - regval = clamp_val(val, 0, LONG_MAX / 2); - regval = div_u64(regval * 4 * 100 * data->rshunt, data->config->power_calculate_factor * - 1000ULL * INA238_FIXED_SHUNT * data->gain); - regval = clamp_val(regval >> 8, 0, U16_MAX); + val = clamp_val(val, 0, LONG_MAX / 2); + val = DIV_ROUND_CLOSEST(val, data->power_lsb); + val = clamp_val(val >> 8, 0, U16_MAX); - return regmap_write(data->regmap, INA238_POWER_LIMIT, regval); + return regmap_write(data->regmap, INA238_POWER_LIMIT, val); } static int ina238_temp_from_reg(s16 regval, u8 resolution) @@ -584,8 +580,7 @@ static ssize_t energy1_input_show(struct device *dev, return ret; /* result in uJ */ - energy = div_u64(regval * INA238_FIXED_SHUNT * data->gain * 16 * 10 * - data->config->power_calculate_factor, 4 * data->rshunt); + energy = regval * data->energy_lsb; return sysfs_emit(buf, "%llu\n", energy); } @@ -817,6 +812,18 @@ static int ina238_probe(struct i2c_client *client) return -ENODEV; } + if (data->config->current_lsb) + data->current_lsb = data->config->current_lsb; + else + data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain, + data->rshunt); + + data->power_lsb = DIV_ROUND_CLOSEST(data->current_lsb * + data->config->power_calculate_factor, + 100); + + data->energy_lsb = data->power_lsb * 16; + hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, &ina238_chip_info, data->config->has_energy ? -- cgit v1.2.3 From 7e420b6a274206f339fe3617943ba1ef9dd1fcb0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sat, 30 Aug 2025 21:48:29 -0700 Subject: hwmon: (ina238) Simplify voltage register accesses Calculate voltage LSB values in the probe function and use throughout the code. Use a single function to read all voltages, independently of the register width. Use the pre-calculated LSB values to convert register values to voltages and do not rely on runtime chip specific code. Use ROUND_CLOSEST functions instead of divide operations to reduce rounding errors. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 161 ++++++++++++++++--------------------------------- 1 file changed, 53 insertions(+), 108 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 316a7dc720f5..ae27ae2582f2 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -101,9 +101,11 @@ #define INA238_CALIBRATION_VALUE 16384 #define INA238_FIXED_SHUNT 20000 -#define INA238_SHUNT_VOLTAGE_LSB 5 /* 5 uV/lsb */ -#define INA238_BUS_VOLTAGE_LSB 3125 /* 3.125 mV/lsb */ -#define SQ52206_BUS_VOLTAGE_LSB 3750 /* 3.75 mV/lsb */ +#define INA238_SHUNT_VOLTAGE_LSB 5000 /* 5 uV/lsb, in nV */ +#define INA238_BUS_VOLTAGE_LSB 3125000 /* 3.125 mV/lsb, in nV */ +#define SQ52206_BUS_VOLTAGE_LSB 3750000 /* 3.75 mV/lsb, in nV */ + +#define NUNIT_PER_MUNIT 1000000 /* n[AV] -> m[AV] */ static const struct regmap_config ina238_regmap_config = { .max_register = INA238_REGISTERS, @@ -118,9 +120,9 @@ struct ina238_config { bool has_power_highest; /* chip detection power peak */ bool has_energy; /* chip detection energy */ u8 temp_resolution; /* temperature register resolution in bit */ - u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */ u16 config_default; /* Power-on default state */ - int bus_voltage_lsb; /* use for temperature calculate, uV/lsb */ + u32 power_calculate_factor; /* fixed parameter for power calculation, from datasheet */ + u32 bus_voltage_lsb; /* bus voltage LSB, in nV */ int current_lsb; /* current LSB, in uA */ }; @@ -131,6 +133,7 @@ struct ina238_data { struct regmap *regmap; u32 rshunt; int gain; + u32 voltage_lsb[2]; /* shunt, bus voltage LSB, in nV */ int current_lsb; /* current LSB, in uA */ int power_lsb; /* power LSB, in uW */ int energy_lsb; /* energy LSB, in uJ */ @@ -226,45 +229,28 @@ static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *v return 0; } -static int ina228_read_shunt_voltage(struct device *dev, u32 attr, int channel, - long *val) -{ - struct ina238_data *data = dev_get_drvdata(dev); - int regval; - int err; - - err = ina238_read_field_s20(data->client, INA238_SHUNT_VOLTAGE, ®val); - if (err) - return err; - - /* - * gain of 1 -> LSB / 4 - * This field has 16 bit on ina238. ina228 adds another 4 bits of - * precision. ina238 conversion factors can still be applied when - * dividing by 16. - */ - *val = (regval * INA238_SHUNT_VOLTAGE_LSB) * data->gain / (1000 * 4) / 16; - return 0; -} - -static int ina228_read_bus_voltage(struct device *dev, u32 attr, int channel, - long *val) +static int ina228_read_voltage(struct ina238_data *data, int channel, long *val) { - struct ina238_data *data = dev_get_drvdata(dev); - int regval; - int err; + int reg = channel ? INA238_BUS_VOLTAGE : INA238_SHUNT_VOLTAGE; + u32 lsb = data->voltage_lsb[channel]; + u32 factor = NUNIT_PER_MUNIT; + int err, regval; - err = ina238_read_field_s20(data->client, INA238_BUS_VOLTAGE, ®val); - if (err) - return err; + if (data->config->has_20bit_voltage_current) { + err = ina238_read_field_s20(data->client, reg, ®val); + if (err) + return err; + /* Adjust accuracy: LSB in units of 500 pV */ + lsb /= 8; + factor *= 2; + } else { + err = regmap_read(data->regmap, reg, ®val); + if (err) + return err; + regval = (s16)regval; + } - /* - * gain of 1 -> LSB / 4 - * This field has 16 bit on ina238. ina228 adds another 4 bits of - * precision. ina238 conversion factors can still be applied when - * dividing by 16. - */ - *val = (regval * data->config->bus_voltage_lsb) / 1000 / 16; + *val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, factor); return 0; } @@ -272,18 +258,16 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, long *val) { struct ina238_data *data = dev_get_drvdata(dev); - int reg, mask; + int reg, mask = 0; int regval; int err; + if (attr == hwmon_in_input) + return ina228_read_voltage(data, channel, val); + switch (channel) { case 0: switch (attr) { - case hwmon_in_input: - if (data->config->has_20bit_voltage_current) - return ina228_read_shunt_voltage(dev, attr, channel, val); - reg = INA238_SHUNT_VOLTAGE; - break; case hwmon_in_max: reg = INA238_SHUNT_OVER_VOLTAGE; break; @@ -304,11 +288,6 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, break; case 1: switch (attr) { - case hwmon_in_input: - if (data->config->has_20bit_voltage_current) - return ina228_read_bus_voltage(dev, attr, channel, val); - reg = INA238_BUS_VOLTAGE; - break; case hwmon_in_max: reg = INA238_BUS_OVER_VOLTAGE; break; @@ -335,72 +314,35 @@ static int ina238_read_in(struct device *dev, u32 attr, int channel, if (err < 0) return err; - switch (attr) { - case hwmon_in_input: - case hwmon_in_max: - case hwmon_in_min: - /* signed register, value in mV */ - regval = (s16)regval; - if (channel == 0) - /* gain of 1 -> LSB / 4 */ - *val = (regval * INA238_SHUNT_VOLTAGE_LSB) * - data->gain / (1000 * 4); - else - *val = (regval * data->config->bus_voltage_lsb) / 1000; - break; - case hwmon_in_max_alarm: - case hwmon_in_min_alarm: + if (mask) *val = !!(regval & mask); - break; - } + else + *val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->voltage_lsb[channel], + NUNIT_PER_MUNIT); return 0; } -static int ina238_write_in(struct device *dev, u32 attr, int channel, - long val) +static int ina238_write_in(struct device *dev, u32 attr, int channel, long val) { struct ina238_data *data = dev_get_drvdata(dev); + static const int low_limits[2] = {-164, 0}; + static const int high_limits[2] = {164, 150000}; + static const u8 low_regs[2] = {INA238_SHUNT_UNDER_VOLTAGE, INA238_BUS_UNDER_VOLTAGE}; + static const u8 high_regs[2] = {INA238_SHUNT_OVER_VOLTAGE, INA238_BUS_OVER_VOLTAGE}; int regval; - if (attr != hwmon_in_max && attr != hwmon_in_min) - return -EOPNOTSUPP; - - /* convert decimal to register value */ - switch (channel) { - case 0: - /* signed value, clamp to max range +/-163 mV */ - regval = clamp_val(val, -163, 163); - regval = (regval * 1000 * 4) / - (INA238_SHUNT_VOLTAGE_LSB * data->gain); - regval = clamp_val(regval, S16_MIN, S16_MAX) & 0xffff; - - switch (attr) { - case hwmon_in_max: - return regmap_write(data->regmap, - INA238_SHUNT_OVER_VOLTAGE, regval); - case hwmon_in_min: - return regmap_write(data->regmap, - INA238_SHUNT_UNDER_VOLTAGE, regval); - default: - return -EOPNOTSUPP; - } - case 1: - /* signed value, positive values only. Clamp to max 102.396 V */ - regval = clamp_val(val, 0, 102396); - regval = (regval * 1000) / data->config->bus_voltage_lsb; - regval = clamp_val(regval, 0, S16_MAX); + /* Initial clamp to avoid overflows */ + val = clamp_val(val, low_limits[channel], high_limits[channel]); + val = DIV_S64_ROUND_CLOSEST((s64)val * NUNIT_PER_MUNIT, data->voltage_lsb[channel]); + /* Final clamp to register limits */ + regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff; - switch (attr) { - case hwmon_in_max: - return regmap_write(data->regmap, - INA238_BUS_OVER_VOLTAGE, regval); - case hwmon_in_min: - return regmap_write(data->regmap, - INA238_BUS_UNDER_VOLTAGE, regval); - default: - return -EOPNOTSUPP; - } + switch (attr) { + case hwmon_in_min: + return regmap_write(data->regmap, low_regs[channel], regval); + case hwmon_in_max: + return regmap_write(data->regmap, high_regs[channel], regval); default: return -EOPNOTSUPP; } @@ -812,6 +754,9 @@ static int ina238_probe(struct i2c_client *client) return -ENODEV; } + data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4; + data->voltage_lsb[1] = data->config->bus_voltage_lsb; + if (data->config->current_lsb) data->current_lsb = data->config->current_lsb; else -- cgit v1.2.3 From bcac89eebefbcf886c2b971de4bf026073e0ee8e Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 3 Sep 2025 16:02:48 -0700 Subject: hwmon: (ina238) Improve current dynamic range The best possible dynamic range for current measurements is achieved if the shunt register value matches the current register value. Adjust the calibration register as well as fixed and default shunt resistor values accordingly to achieve this range. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 51 +++++++++++++++++++------------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index ae27ae2582f2..c04481a8c643 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -51,7 +51,7 @@ #define INA238_REGISTERS 0x20 -#define INA238_RSHUNT_DEFAULT 10000 /* uOhm */ +#define INA238_RSHUNT_DEFAULT 2500 /* uOhm */ /* Default configuration of device on reset. */ #define INA238_CONFIG_DEFAULT 0 @@ -67,39 +67,26 @@ * relative to the shunt resistor value within the driver. This is similar to * how the ina2xx driver handles current/power scaling. * - * The end result of this is that increasing shunt values (from a fixed 20 mOhm - * shunt) increase the effective current/power accuracy whilst limiting the - * range and decreasing shunt values decrease the effective accuracy but - * increase the range. + * To achieve the best possible dynamic range, the value of the shunt voltage + * register should match the value of the current register. With that, the shunt + * voltage of 0x7fff = 32,767 uV = 163,785 uV matches the maximum current, + * and no accuracy is lost. Experiments with a real chip show that this is + * achieved by setting the SHUNT_CAL register to a value of 0x1000 = 4,096. + * Per datasheet, + * SHUNT_CAL = 819.2 x 10^6 x CURRENT_LSB x Rshunt + * = 819,200,000 x CURRENT_LSB x Rshunt + * With SHUNT_CAL set to 4,096, we get + * CURRENT_LSB = 4,096 / (819,200,000 x Rshunt) + * Assuming an Rshunt value of 5 mOhm, we get + * CURRENT_LSB = 4,096 / (819,200,000 x 0.005) = 1mA + * and thus a dynamic range of 1mA ... 32,767mA, which is sufficient for most + * applications. The actual dynamic range is of course determined by the actual + * shunt resistor value. * - * The value of the Current register is calculated given the following: - * Current (A) = (shunt voltage register * 5) * calibration / 81920 - * - * The maximum shunt voltage is 163.835 mV (0x7fff, ADC_RANGE = 0, gain = 4). - * With the maximum current value of 0x7fff and a fixed shunt value results in - * a calibration value of 16384 (0x4000). - * - * 0x7fff = (0x7fff * 5) * calibration / 81920 - * calibration = 0x4000 - * - * Equivalent calibration is applied for the Power register (maximum value for - * bus voltage is 102396.875 mV, 0x7fff), where the maximum power that can - * occur is ~16776192 uW (register value 0x147a8): - * - * This scaling means the resulting values for Current and Power registers need - * to be scaled by the difference between the fixed shunt resistor and the - * actual shunt resistor: - * - * shunt = 0x4000 / (819.2 * 10^6) / 0.001 = 20000 uOhms (with 1mA/lsb) - * - * Current (mA) = register value * 20000 / rshunt / 4 * gain - * Power (mW) = 0.2 * register value * 20000 / rshunt / 4 * gain - * (Specific for SQ52206) - * Power (mW) = 0.24 * register value * 20000 / rshunt / 4 * gain - * Energy (uJ) = 16 * 0.24 * register value * 20000 / rshunt / 4 * gain * 1000 + * Power and energy values are scaled accordingly. */ -#define INA238_CALIBRATION_VALUE 16384 -#define INA238_FIXED_SHUNT 20000 +#define INA238_CALIBRATION_VALUE 4096 +#define INA238_FIXED_SHUNT 5000 #define INA238_SHUNT_VOLTAGE_LSB 5000 /* 5 uV/lsb, in nV */ #define INA238_BUS_VOLTAGE_LSB 3125000 /* 3.125 mV/lsb, in nV */ -- cgit v1.2.3 From b7bce92f2890f6002bd3155c0510bd28b6ead4e1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Wed, 3 Sep 2025 16:08:03 -0700 Subject: hwmon: (ina238) Stop using the shunt voltage register Since the value of the current register and the value of the shunt register now match each other, it is no longer necessary to read the shunt voltage register in the first place. Read the current register instead and use it to calculate the shunt voltage. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index c04481a8c643..9dc94eccb750 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -218,7 +218,7 @@ static int ina238_read_field_s20(const struct i2c_client *client, u8 reg, s32 *v static int ina228_read_voltage(struct ina238_data *data, int channel, long *val) { - int reg = channel ? INA238_BUS_VOLTAGE : INA238_SHUNT_VOLTAGE; + int reg = channel ? INA238_BUS_VOLTAGE : INA238_CURRENT; u32 lsb = data->voltage_lsb[channel]; u32 factor = NUNIT_PER_MUNIT; int err, regval; -- cgit v1.2.3 From a1d5f8ecb934066220b4cb1b8ba831a8726bf52f Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 09:25:16 -0700 Subject: hwmon: (ina238) Add support for current limits Since the shunt voltage register and the current register now report the same values, use the shunt voltage limit registers to report and adjust current limits, using the same LSB as the LSB used for the actual current register. Handle current register accuracy differences in separate function to improve code readability. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 4 ++ drivers/hwmon/ina238.c | 105 ++++++++++++++++++++++++++++++++--------- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index 1ac4e2c419ac..3c7db4a47056 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -82,6 +82,10 @@ power1_input_highest Peak Power (uW) (SQ52206 only) curr1_input Current measurement (mA) +curr1_min Minimum current threshold (mA) +curr1_min_alarm Minimum current alarm +curr1_max Maximum current threshold (mA) +curr1_max_alarm Maximum current alarm energy1_input Energy measurement (uJ) (SQ52206 and INA237 only) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 9dc94eccb750..97f12efcaef4 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -335,40 +335,92 @@ static int ina238_write_in(struct device *dev, u32 attr, int channel, long val) } } -static int ina238_read_current(struct device *dev, u32 attr, long *val) +static int __ina238_read_curr(struct ina238_data *data, long *val) +{ + u32 lsb = data->current_lsb; + int err, regval; + + if (data->config->has_20bit_voltage_current) { + err = ina238_read_field_s20(data->client, INA238_CURRENT, ®val); + if (err) + return err; + lsb /= 16; /* Adjust accuracy */ + } else { + err = regmap_read(data->regmap, INA238_CURRENT, ®val); + if (err) + return err; + regval = (s16)regval; + } + + *val = DIV_S64_ROUND_CLOSEST((s64)regval * lsb, 1000); + return 0; +} + +static int ina238_read_curr(struct device *dev, u32 attr, long *val) { struct ina238_data *data = dev_get_drvdata(dev); + int reg, mask = 0; int regval; int err; - switch (attr) { - case hwmon_curr_input: - if (data->config->has_20bit_voltage_current) { - err = ina238_read_field_s20(data->client, INA238_CURRENT, ®val); - if (err) - return err; - } else { - err = regmap_read(data->regmap, INA238_CURRENT, ®val); - if (err < 0) - return err; - /* sign-extend */ - regval = (s16)regval; - } + if (attr == hwmon_curr_input) + return __ina238_read_curr(data, val); - /* Signed register. Result in mA */ - *val = DIV_S64_ROUND_CLOSEST((s64)regval * data->current_lsb, 1000); - - /* Account for 4 bit offset */ - if (data->config->has_20bit_voltage_current) - *val /= 16; + switch (attr) { + case hwmon_curr_min: + reg = INA238_SHUNT_UNDER_VOLTAGE; + break; + case hwmon_curr_min_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTUL; + break; + case hwmon_curr_max: + reg = INA238_SHUNT_OVER_VOLTAGE; + break; + case hwmon_curr_max_alarm: + reg = INA238_DIAG_ALERT; + mask = INA238_DIAG_ALERT_SHNTOL; break; default: return -EOPNOTSUPP; } + err = regmap_read(data->regmap, reg, ®val); + if (err < 0) + return err; + + if (mask) + *val = !!(regval & mask); + else + *val = DIV_S64_ROUND_CLOSEST((s64)(s16)regval * data->current_lsb, 1000); + return 0; } +static int ina238_write_curr(struct device *dev, u32 attr, long val) +{ + struct ina238_data *data = dev_get_drvdata(dev); + int regval; + + /* Set baseline range to avoid over/underflows */ + val = clamp_val(val, -1000000, 1000000); + /* Scale */ + val = DIV_ROUND_CLOSEST(val * 1000, data->current_lsb); + /* Clamp to register size */ + regval = clamp_val(val, S16_MIN, S16_MAX) & 0xffff; + + switch (attr) { + case hwmon_curr_min: + return regmap_write(data->regmap, INA238_SHUNT_UNDER_VOLTAGE, + regval); + case hwmon_curr_max: + return regmap_write(data->regmap, INA238_SHUNT_OVER_VOLTAGE, + regval); + default: + return -EOPNOTSUPP; + } +} + static int ina238_read_power(struct device *dev, u32 attr, long *val) { struct ina238_data *data = dev_get_drvdata(dev); @@ -521,7 +573,7 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type, case hwmon_in: return ina238_read_in(dev, attr, channel, val); case hwmon_curr: - return ina238_read_current(dev, attr, val); + return ina238_read_curr(dev, attr, val); case hwmon_power: return ina238_read_power(dev, attr, val); case hwmon_temp: @@ -544,6 +596,9 @@ static int ina238_write(struct device *dev, enum hwmon_sensor_types type, case hwmon_in: err = ina238_write_in(dev, attr, channel, val); break; + case hwmon_curr: + err = ina238_write_curr(dev, attr, val); + break; case hwmon_power: err = ina238_write_power_max(dev, val); break; @@ -582,7 +637,12 @@ static umode_t ina238_is_visible(const void *drvdata, case hwmon_curr: switch (attr) { case hwmon_curr_input: + case hwmon_curr_max_alarm: + case hwmon_curr_min_alarm: return 0444; + case hwmon_curr_max: + case hwmon_curr_min: + return 0644; default: return 0; } @@ -627,7 +687,8 @@ static const struct hwmon_channel_info * const ina238_info[] = { INA238_HWMON_IN_CONFIG), HWMON_CHANNEL_INFO(curr, /* 0: current through shunt */ - HWMON_C_INPUT), + HWMON_C_INPUT | HWMON_C_MIN | HWMON_C_MIN_ALARM | + HWMON_C_MAX | HWMON_C_MAX_ALARM), HWMON_CHANNEL_INFO(power, /* 0: power */ HWMON_P_INPUT | HWMON_P_MAX | -- cgit v1.2.3 From e7702d72c3e3fcc6017d29bba420deb04da3e1d6 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 09:34:13 -0700 Subject: hwmon: (ina238) Order chip information alphabetically Order chip type enum and chip configuration data alphabetically to simplify adding support for additional chips. No functional change. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 97f12efcaef4..4681325f58f0 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -100,7 +100,7 @@ static const struct regmap_config ina238_regmap_config = { .val_bits = 16, }; -enum ina238_ids { ina238, ina237, sq52206, ina228 }; +enum ina238_ids { ina228, ina237, ina238, sq52206 }; struct ina238_config { bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */ @@ -127,7 +127,16 @@ struct ina238_data { }; static const struct ina238_config ina238_config[] = { - [ina238] = { + [ina228] = { + .has_20bit_voltage_current = true, + .has_energy = true, + .has_power_highest = false, + .power_calculate_factor = 20, + .config_default = INA238_CONFIG_DEFAULT, + .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, + .temp_resolution = 16, + }, + [ina237] = { .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, @@ -136,7 +145,7 @@ static const struct ina238_config ina238_config[] = { .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, .temp_resolution = 12, }, - [ina237] = { + [ina238] = { .has_20bit_voltage_current = false, .has_energy = false, .has_power_highest = false, @@ -154,15 +163,6 @@ static const struct ina238_config ina238_config[] = { .bus_voltage_lsb = SQ52206_BUS_VOLTAGE_LSB, .temp_resolution = 16, }, - [ina228] = { - .has_20bit_voltage_current = true, - .has_energy = true, - .has_power_highest = false, - .power_calculate_factor = 20, - .config_default = INA238_CONFIG_DEFAULT, - .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, - .temp_resolution = 16, - }, }; static int ina238_read_reg24(const struct i2c_client *client, u8 reg, u32 *val) -- cgit v1.2.3 From cc67b875c9e40ef7628219a37447c477d5324dc1 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 15:06:05 -0700 Subject: hwmon: (ina238) Use the energy64 attribute type to report the energy Use the energy64 attribute type instead of locally defined sysfs attributes to report the accumulated energy. No functional change intended. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 4681325f58f0..4d5b383b2521 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -548,22 +548,19 @@ static int ina238_write_temp_max(struct device *dev, long val) return regmap_write(data->regmap, INA238_TEMP_LIMIT, regval); } -static ssize_t energy1_input_show(struct device *dev, - struct device_attribute *da, char *buf) +static int ina238_read_energy(struct device *dev, s64 *energy) { struct ina238_data *data = dev_get_drvdata(dev); - int ret; u64 regval; - u64 energy; + int ret; ret = ina238_read_reg40(data->client, SQ52206_ENERGY, ®val); if (ret) return ret; /* result in uJ */ - energy = regval * data->energy_lsb; - - return sysfs_emit(buf, "%llu\n", energy); + *energy = regval * data->energy_lsb; + return 0; } static int ina238_read(struct device *dev, enum hwmon_sensor_types type, @@ -576,6 +573,8 @@ static int ina238_read(struct device *dev, enum hwmon_sensor_types type, return ina238_read_curr(dev, attr, val); case hwmon_power: return ina238_read_power(dev, attr, val); + case hwmon_energy64: + return ina238_read_energy(dev, (s64 *)val); case hwmon_temp: return ina238_read_temp(dev, attr, val); default: @@ -620,6 +619,7 @@ static umode_t ina238_is_visible(const void *drvdata, { const struct ina238_data *data = drvdata; bool has_power_highest = data->config->has_power_highest; + bool has_energy = data->config->has_energy; switch (type) { case hwmon_in: @@ -660,6 +660,11 @@ static umode_t ina238_is_visible(const void *drvdata, default: return 0; } + case hwmon_energy64: + /* hwmon_energy_input */ + if (has_energy) + return 0444; + return 0; case hwmon_temp: switch (attr) { case hwmon_temp_input: @@ -693,6 +698,8 @@ static const struct hwmon_channel_info * const ina238_info[] = { /* 0: power */ HWMON_P_INPUT | HWMON_P_MAX | HWMON_P_MAX_ALARM | HWMON_P_INPUT_HIGHEST), + HWMON_CHANNEL_INFO(energy64, + HWMON_E_INPUT), HWMON_CHANNEL_INFO(temp, /* 0: die temperature */ HWMON_T_INPUT | HWMON_T_MAX | HWMON_T_MAX_ALARM), @@ -710,15 +717,6 @@ static const struct hwmon_chip_info ina238_chip_info = { .info = ina238_info, }; -/* energy attributes are 5 bytes wide so we need u64 */ -static DEVICE_ATTR_RO(energy1_input); - -static struct attribute *ina238_attrs[] = { - &dev_attr_energy1_input.attr, - NULL, -}; -ATTRIBUTE_GROUPS(ina238); - static int ina238_probe(struct i2c_client *client) { struct device *dev = &client->dev; @@ -818,9 +816,7 @@ static int ina238_probe(struct i2c_client *client) data->energy_lsb = data->power_lsb * 16; hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data, - &ina238_chip_info, - data->config->has_energy ? - ina238_groups : NULL); + &ina238_chip_info, NULL); if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); -- cgit v1.2.3 From 807e315bf9509f4d57eca687c9732ce16c3ef843 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 1 Sep 2025 08:37:15 -0700 Subject: hwmon: (ina238) Support active-high alert polarity All chips supported by this driver support configurable active-high alert priority. This is already documented in the devicetree description. Add support for it to the driver. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 4d5b383b2521..24e396c69ae2 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -60,6 +60,7 @@ #define INA238_ADC_CONFIG_DEFAULT 0xfb6a /* Configure alerts to be based on averaged value (SLOWALERT) */ #define INA238_DIAG_ALERT_DEFAULT 0x2000 +#define INA238_DIAG_ALERT_APOL BIT(12) /* * This driver uses a fixed calibration value in order to scale current/power * based on a fixed shunt resistor value. This allows for conversion within the @@ -793,8 +794,11 @@ static int ina238_probe(struct i2c_client *client) } /* Setup alert/alarm configuration */ - ret = regmap_write(data->regmap, INA238_DIAG_ALERT, - INA238_DIAG_ALERT_DEFAULT); + config = INA238_DIAG_ALERT_DEFAULT; + if (device_property_read_bool(dev, "ti,alert-polarity-active-high")) + config |= INA238_DIAG_ALERT_APOL; + + ret = regmap_write(data->regmap, INA238_DIAG_ALERT, config); if (ret < 0) { dev_err(dev, "error configuring the device: %d\n", ret); return -ENODEV; -- cgit v1.2.3 From d153106bd4fd63eb8d3557277a3d4ee5dbbed3b0 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 09:39:53 -0700 Subject: hwmon: (ina238) Only configure calibration and shunt registers if needed Prepare for supporting chips with internal shunt resistor by only setting calibration and shunt resistor registers if no current LSB is configured. Do not display a log message during probe if a chip does not have shunt and gain registers since those would otherwise display 0, and a message just indicating that the driver was loaded would be just noise. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- drivers/hwmon/ina238.c | 82 ++++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 24e396c69ae2..da5b43184dd1 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -745,32 +745,48 @@ static int ina238_probe(struct i2c_client *client) return PTR_ERR(data->regmap); } - /* load shunt value */ - if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0) - data->rshunt = INA238_RSHUNT_DEFAULT; - if (data->rshunt == 0) { - dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); - return -EINVAL; - } - - /* load shunt gain value */ - if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0) - data->gain = 4; /* Default of ADCRANGE = 0 */ - if (data->gain != 1 && data->gain != 2 && data->gain != 4) { - dev_err(dev, "invalid shunt gain value %u\n", data->gain); - return -EINVAL; - } - /* Setup CONFIG register */ config = data->config->config_default; - if (chip == sq52206) { - if (data->gain == 1) - config |= SQ52206_CONFIG_ADCRANGE_HIGH; /* ADCRANGE = 10/11 is /1 */ - else if (data->gain == 2) - config |= SQ52206_CONFIG_ADCRANGE_LOW; /* ADCRANGE = 01 is /2 */ - } else if (data->gain == 1) { - config |= INA238_CONFIG_ADCRANGE; /* ADCRANGE = 1 is /1 */ + if (data->config->current_lsb) { + data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB; + data->current_lsb = data->config->current_lsb; + } else { + /* load shunt value */ + if (device_property_read_u32(dev, "shunt-resistor", &data->rshunt) < 0) + data->rshunt = INA238_RSHUNT_DEFAULT; + if (data->rshunt == 0) { + dev_err(dev, "invalid shunt resister value %u\n", data->rshunt); + return -EINVAL; + } + + /* load shunt gain value */ + if (device_property_read_u32(dev, "ti,shunt-gain", &data->gain) < 0) + data->gain = 4; /* Default of ADCRANGE = 0 */ + if (data->gain != 1 && data->gain != 2 && data->gain != 4) { + dev_err(dev, "invalid shunt gain value %u\n", data->gain); + return -EINVAL; + } + + /* Setup SHUNT_CALIBRATION register with fixed value */ + ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION, + INA238_CALIBRATION_VALUE); + if (ret < 0) { + dev_err(dev, "error configuring the device: %d\n", ret); + return -ENODEV; + } + if (chip == sq52206) { + if (data->gain == 1) /* ADCRANGE = 10/11 is /1 */ + config |= SQ52206_CONFIG_ADCRANGE_HIGH; + else if (data->gain == 2) /* ADCRANGE = 01 is /2 */ + config |= SQ52206_CONFIG_ADCRANGE_LOW; + } else if (data->gain == 1) { /* ADCRANGE = 1 is /1 */ + config |= INA238_CONFIG_ADCRANGE; + } + data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4; + data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain, + data->rshunt); } + ret = regmap_write(data->regmap, INA238_CONFIG, config); if (ret < 0) { dev_err(dev, "error configuring the device: %d\n", ret); @@ -785,14 +801,6 @@ static int ina238_probe(struct i2c_client *client) return -ENODEV; } - /* Setup SHUNT_CALIBRATION register with fixed value */ - ret = regmap_write(data->regmap, INA238_SHUNT_CALIBRATION, - INA238_CALIBRATION_VALUE); - if (ret < 0) { - dev_err(dev, "error configuring the device: %d\n", ret); - return -ENODEV; - } - /* Setup alert/alarm configuration */ config = INA238_DIAG_ALERT_DEFAULT; if (device_property_read_bool(dev, "ti,alert-polarity-active-high")) @@ -804,15 +812,8 @@ static int ina238_probe(struct i2c_client *client) return -ENODEV; } - data->voltage_lsb[0] = INA238_SHUNT_VOLTAGE_LSB * data->gain / 4; data->voltage_lsb[1] = data->config->bus_voltage_lsb; - if (data->config->current_lsb) - data->current_lsb = data->config->current_lsb; - else - data->current_lsb = DIV_U64_ROUND_CLOSEST(250ULL * INA238_FIXED_SHUNT * data->gain, - data->rshunt); - data->power_lsb = DIV_ROUND_CLOSEST(data->current_lsb * data->config->power_calculate_factor, 100); @@ -824,8 +825,9 @@ static int ina238_probe(struct i2c_client *client) if (IS_ERR(hwmon_dev)) return PTR_ERR(hwmon_dev); - dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n", - client->name, data->rshunt, data->gain); + if (data->rshunt) + dev_info(dev, "power monitor %s (Rshunt = %u uOhm, gain = %u)\n", + client->name, data->rshunt, data->gain); return 0; } -- cgit v1.2.3 From 248fd3b96d08c2a5736ca8395470544f23fe1f5c Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 09:39:53 -0700 Subject: hwmon: (ina238) Add support for INA780 INA780 is similar to the other chips in the series, but does not support the shunt voltage register. Shunt voltage limit registers have been renamed to current limit registers, but are otherwise identical. While the chip does not directly report the shunt voltage, report it anyway by calculating its value from the current register. Cc: Chris Packham Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 10 +++++++++- drivers/hwmon/Kconfig | 6 +++--- drivers/hwmon/ina238.c | 17 ++++++++++++++++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index 3c7db4a47056..722760961821 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -32,6 +32,11 @@ Supported chips: Datasheet: https://www.ti.com/lit/gpn/ina238 + * Texas Instruments INA780 + + Datasheet: + https://www.ti.com/product/ina780a + * Silergy SQ52206 Prefix: 'SQ52206' @@ -56,6 +61,9 @@ INA237 is a functionally equivalent variant of INA238 with slightly different accuracy. INA228 is another variant of INA238 with higher ADC resolution. This chip also reports the energy. +INA780 is a variant of the chip series with built-in shunt resistor. +It also reports the energy. + SQ52206 is a mostly compatible chip from Sylergy. It reports the energy as well as the peak power consumption. @@ -88,7 +96,7 @@ curr1_max Maximum current threshold (mA) curr1_max_alarm Maximum current alarm energy1_input Energy measurement (uJ) - (SQ52206 and INA237 only) + (SQ52206, INA237, and INA780 only) temp1_input Die temperature measurement (mC) temp1_max Maximum die temperature threshold (mC) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 6bdce991f5f0..c044c26fbd11 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2256,9 +2256,9 @@ config SENSORS_INA238 depends on I2C select REGMAP_I2C help - If you say yes here you get support for INA228, INA237, INA238, and - SQ52206 power monitor chips. This driver supports voltage, current, - power, energy, and temperature measurements as well as alarm + If you say yes here you get support for INA228, INA237, INA238, + INA780, and SQ52206 power monitor chips. This driver supports voltage, + current, power, energy, and temperature measurements as well as alarm configuration. This driver can also be built as a module. If so, the module diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index da5b43184dd1..98255619adeb 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -101,7 +101,7 @@ static const struct regmap_config ina238_regmap_config = { .val_bits = 16, }; -enum ina238_ids { ina228, ina237, ina238, sq52206 }; +enum ina238_ids { ina228, ina237, ina238, ina780, sq52206 }; struct ina238_config { bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */ @@ -155,6 +155,16 @@ static const struct ina238_config ina238_config[] = { .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, .temp_resolution = 12, }, + [ina780] = { + .has_20bit_voltage_current = false, + .has_energy = true, + .has_power_highest = false, + .power_calculate_factor = 20, + .config_default = INA238_CONFIG_DEFAULT, + .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, + .temp_resolution = 12, + .current_lsb = 2400, + }, [sq52206] = { .has_20bit_voltage_current = false, .has_energy = true, @@ -836,6 +846,7 @@ static const struct i2c_device_id ina238_id[] = { { "ina228", ina228 }, { "ina237", ina237 }, { "ina238", ina238 }, + { "ina780", ina780 }, { "sq52206", sq52206 }, { } }; @@ -854,6 +865,10 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = { .compatible = "ti,ina238", .data = (void *)ina238 }, + { + .compatible = "ti,ina780", + .data = (void *)ina780 + }, { .compatible = "silergy,sq52206", .data = (void *)sq52206 -- cgit v1.2.3 From 7942ca9a475115b9668e53c2d9cfb57a51d62e04 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Mon, 1 Sep 2025 09:02:07 -0700 Subject: dt-bindings: hwmon: ti,ina2xx: Add INA700 Add a compatible string for INA700. The chip is register compatible with INA780 but implements different ADC ranges and thus needs a separate compatible entry. Cc: Christian Kahr Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Reviewed-by: Krzysztof Kozlowski Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml index 8b491be9c49d..d3cde8936686 100644 --- a/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml +++ b/Documentation/devicetree/bindings/hwmon/ti,ina2xx.yaml @@ -32,6 +32,7 @@ properties: - ti,ina237 - ti,ina238 - ti,ina260 + - ti,ina700 - ti,ina780 reg: @@ -115,6 +116,7 @@ allOf: - ti,ina237 - ti,ina238 - ti,ina260 + - ti,ina700 - ti,ina780 then: properties: @@ -133,6 +135,7 @@ allOf: - ti,ina230 - ti,ina231 - ti,ina260 + - ti,ina700 - ti,ina780 then: properties: @@ -143,6 +146,7 @@ allOf: compatible: contains: enum: + - ti,ina700 - ti,ina780 then: properties: -- cgit v1.2.3 From 273bfedc003c40bb0fb471841c62f4fc7063be83 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Sun, 31 Aug 2025 17:54:16 -0700 Subject: hwmon: (ina238) Add support for INA700 INA700 is register compatible to INA780 but has different current, power, and energy LSB values. While the chip does not directly report the shunt voltage, report it anyway by calculating its value from the current register. Reviewed-by: Chris Packham Tested-by: Chris Packham # INA780 Cc: Christian Kahr Signed-off-by: Guenter Roeck --- Documentation/hwmon/ina238.rst | 9 +++++++-- drivers/hwmon/Kconfig | 6 +++--- drivers/hwmon/ina238.c | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Documentation/hwmon/ina238.rst b/Documentation/hwmon/ina238.rst index 722760961821..43950d1ec551 100644 --- a/Documentation/hwmon/ina238.rst +++ b/Documentation/hwmon/ina238.rst @@ -32,6 +32,11 @@ Supported chips: Datasheet: https://www.ti.com/lit/gpn/ina238 + * Texas Instruments INA700 + + Datasheet: + https://www.ti.com/product/ina700 + * Texas Instruments INA780 Datasheet: @@ -61,8 +66,8 @@ INA237 is a functionally equivalent variant of INA238 with slightly different accuracy. INA228 is another variant of INA238 with higher ADC resolution. This chip also reports the energy. -INA780 is a variant of the chip series with built-in shunt resistor. -It also reports the energy. +INA700 and INA780 are variants of the chip series with built-in shunt resistor. +They also report the energy. SQ52206 is a mostly compatible chip from Sylergy. It reports the energy as well as the peak power consumption. diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c044c26fbd11..8a41275ca518 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -2257,9 +2257,9 @@ config SENSORS_INA238 select REGMAP_I2C help If you say yes here you get support for INA228, INA237, INA238, - INA780, and SQ52206 power monitor chips. This driver supports voltage, - current, power, energy, and temperature measurements as well as alarm - configuration. + INA700, INA780, and SQ52206 power monitor chips. This driver supports + voltage, current, power, energy, and temperature measurements as well + as alarm configuration. This driver can also be built as a module. If so, the module will be called ina238. diff --git a/drivers/hwmon/ina238.c b/drivers/hwmon/ina238.c index 98255619adeb..356d19b7675c 100644 --- a/drivers/hwmon/ina238.c +++ b/drivers/hwmon/ina238.c @@ -101,7 +101,7 @@ static const struct regmap_config ina238_regmap_config = { .val_bits = 16, }; -enum ina238_ids { ina228, ina237, ina238, ina780, sq52206 }; +enum ina238_ids { ina228, ina237, ina238, ina700, ina780, sq52206 }; struct ina238_config { bool has_20bit_voltage_current; /* vshunt, vbus and current are 20-bit fields */ @@ -155,6 +155,16 @@ static const struct ina238_config ina238_config[] = { .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, .temp_resolution = 12, }, + [ina700] = { + .has_20bit_voltage_current = false, + .has_energy = true, + .has_power_highest = false, + .power_calculate_factor = 20, + .config_default = INA238_CONFIG_DEFAULT, + .bus_voltage_lsb = INA238_BUS_VOLTAGE_LSB, + .temp_resolution = 12, + .current_lsb = 480, + }, [ina780] = { .has_20bit_voltage_current = false, .has_energy = true, @@ -846,6 +856,7 @@ static const struct i2c_device_id ina238_id[] = { { "ina228", ina228 }, { "ina237", ina237 }, { "ina238", ina238 }, + { "ina700", ina700 }, { "ina780", ina780 }, { "sq52206", sq52206 }, { } @@ -865,6 +876,10 @@ static const struct of_device_id __maybe_unused ina238_of_match[] = { .compatible = "ti,ina238", .data = (void *)ina238 }, + { + .compatible = "ti,ina700", + .data = (void *)ina700 + }, { .compatible = "ti,ina780", .data = (void *)ina780 -- cgit v1.2.3 From 55cb81254333ab90985a9e861d33c6f8e1cf0118 Mon Sep 17 00:00:00 2001 From: Maciej Zonski Date: Sat, 6 Sep 2025 18:17:37 +0200 Subject: hwmon: (asus-ec-sensors) add ROG STRIX X870-I GAMING WIFI Add support for ROG STRIX X870-I GAMING WIFI Signed-off-by: Maciej Zonski Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250906161748.219567-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index baf9eba5957c..6a9c5f30e016 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -34,6 +34,7 @@ Supported boards: * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING * ROG STRIX X670E-I GAMING WIFI + * ROG STRIX X870-I GAMING WIFI * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index f580ff2e212f..32d4dd26aa84 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -621,6 +621,13 @@ static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = { .family = family_amd_600_series, }; +static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +}; + static const struct ec_board_info board_info_strix_z390_f_gaming = { .sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | @@ -759,6 +766,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x570_i_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI", &board_info_strix_x670e_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI", + &board_info_strix_x870_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", -- cgit v1.2.3 From 552e369db339c4d9f29a5569ec2d661cb4353f40 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 7 Sep 2025 12:20:20 +0200 Subject: hwmon: (nzxt-smart2) Use devm_mutex_init() Use devm_mutex_init() instead of hand-writing it. This saves some LoC, improves readability and saves some space in the generated .o file. Before: ====== text data bss dec hex filename 25878 11329 128 37335 91d7 drivers/hwmon/nzxt-smart2.o After: ===== text data bss dec hex filename 25551 11257 128 36936 9048 drivers/hwmon/nzxt-smart2.o Signed-off-by: Christophe JAILLET Link: https://lore.kernel.org/r/f51fac0871ec7dbe4e28447ee4f774d028a53426.1757240403.git.christophe.jaillet@wanadoo.fr Signed-off-by: Guenter Roeck --- drivers/hwmon/nzxt-smart2.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/hwmon/nzxt-smart2.c b/drivers/hwmon/nzxt-smart2.c index c2d1173f42fe..58ef9fa0184b 100644 --- a/drivers/hwmon/nzxt-smart2.c +++ b/drivers/hwmon/nzxt-smart2.c @@ -721,11 +721,6 @@ static int __maybe_unused nzxt_smart2_hid_reset_resume(struct hid_device *hdev) return init_device(drvdata, drvdata->update_interval); } -static void mutex_fini(void *lock) -{ - mutex_destroy(lock); -} - static int nzxt_smart2_hid_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -741,8 +736,7 @@ static int nzxt_smart2_hid_probe(struct hid_device *hdev, init_waitqueue_head(&drvdata->wq); - mutex_init(&drvdata->mutex); - ret = devm_add_action_or_reset(&hdev->dev, mutex_fini, &drvdata->mutex); + ret = devm_mutex_init(&hdev->dev, &drvdata->mutex); if (ret) return ret; -- cgit v1.2.3 From c97c66e04c2294d59827835e6fd46e0e4a5e2c06 Mon Sep 17 00:00:00 2001 From: Wensheng Wang Date: Tue, 5 Aug 2025 18:20:18 +0800 Subject: dt-bindings: hwmon: Add MPS mp2869,mp29608,mp29612,mp29816 and mp29502 Add support for MPS mp2869/mp2869a,mp29608/mp29608a,mp29612/mp29612a, mp29816/mp29816a/mp29816b/mp29816c and mp29502 controller. Acked-by: Rob Herring (Arm) Signed-off-by: Wensheng Wang Link: https://lore.kernel.org/r/20250805102020.749850-1-wenswang@yeah.net Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index f3dd18681aa6..0e6ba6e12a63 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -293,10 +293,20 @@ properties: - mps,mp2856 # Monolithic Power Systems Inc. multi-phase controller mp2857 - mps,mp2857 + # Monolithic Power Systems Inc. multi-phase controller mp2869 + - mps,mp2869 # Monolithic Power Systems Inc. multi-phase controller mp2888 - mps,mp2888 # Monolithic Power Systems Inc. multi-phase controller mp2891 - mps,mp2891 + # Monolithic Power Systems Inc. multi-phase controller mp29502 + - mps,mp29502 + # Monolithic Power Systems Inc. multi-phase controller mp29608 + - mps,mp29608 + # Monolithic Power Systems Inc. multi-phase controller mp29612 + - mps,mp29612 + # Monolithic Power Systems Inc. multi-phase controller mp29816 + - mps,mp29816 # Monolithic Power Systems Inc. multi-phase controller mp2993 - mps,mp2993 # Monolithic Power Systems Inc. hot-swap protection device -- cgit v1.2.3 From a3a2923aaf7f2cf3aaa4649bddee2f936751825f Mon Sep 17 00:00:00 2001 From: Wensheng Wang Date: Tue, 5 Aug 2025 18:20:19 +0800 Subject: hwmon: add MP2869,MP29608,MP29612 and MP29816 series driver Add support for MPS VR mp2869/mp2869a,mp29608/mp29608a,mp29612/mp29612a and mp29816/mp29816a/mp29816b/mp29816c controller. This driver exposes telemetry and limit value readings and writtings. Signed-off-by: Wensheng Wang Link: https://lore.kernel.org/r/20250805102020.749850-2-wenswang@yeah.net Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mp2869.rst | 175 +++++++++++ MAINTAINERS | 7 + drivers/hwmon/pmbus/Kconfig | 9 + drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mp2869.c | 659 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 852 insertions(+) create mode 100644 Documentation/hwmon/mp2869.rst create mode 100644 drivers/hwmon/pmbus/mp2869.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index d292a86ac5da..36303148dc43 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -173,6 +173,7 @@ Hardware Monitoring Kernel Drivers menf21bmc mlxreg-fan mp2856 + mp2869 mp2888 mp2891 mp2975 diff --git a/Documentation/hwmon/mp2869.rst b/Documentation/hwmon/mp2869.rst new file mode 100644 index 000000000000..2d9d65fc86b6 --- /dev/null +++ b/Documentation/hwmon/mp2869.rst @@ -0,0 +1,175 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mp2869 +==================== + +Supported chips: + + * MPS mp2869 + + Prefix: 'mp2869' + + * MPS mp29608 + + Prefix: 'mp29608' + + * MPS mp29612 + + Prefix: 'mp29612' + + * MPS mp29816 + + Prefix: 'mp29816' + +Author: + + Wensheng Wang + +Description +----------- + +This driver implements support for Monolithic Power Systems, Inc. (MPS) +MP2869 Dual Loop Digital Multi-phase Controller. + +Device compliant with: + +- PMBus rev 1.3 interface. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_crit** + +**in1_crit_alarm** + +**in1_lcrit** + +**in1_lcrit_alarm** + +**in1_min** + +**in1_min_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_crit** + +**in2_crit_alarm** + +**in2_lcrit** + +**in2_lcrit_alarm** + +**in3_input** + +**in3_label** + +**in3_crit** + +**in3_crit_alarm** + +**in3_lcrit** + +**in3_lcrit_alarm** + +The driver provides the following attributes for input current: + +**curr1_input** + +**curr1_label** + +**curr2_input** + +**curr2_label** + +The driver provides the following attributes for output current: + +**curr3_input** + +**curr3_label** + +**curr3_crit** + +**curr3_crit_alarm** + +**curr3_max** + +**curr3_max_alarm** + +**curr4_input** + +**curr4_label** + +**curr4_crit** + +**curr4_crit_alarm** + +**curr4_max** + +**curr4_max_alarm** + +The driver provides the following attributes for input power: + +**power1_input** + +**power1_label** + +**power2_input** + +**power2_label** + +The driver provides the following attributes for output power: + +**power3_input** + +**power3_label** + +**power3_input** + +**power3_label** + +**power3_max** + +**power3_max_alarm** + +**power4_input** + +**power4_label** + +**power4_input** + +**power4_label** + +**power4_max** + +**power4_max_alarm** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_crit** + +**temp1_crit_alarm** + +**temp1_max** + +**temp1_max_alarm** + +**temp2_input** + +**temp2_crit** + +**temp2_crit_alarm** + +**temp2_max** + +**temp2_max_alarm** diff --git a/MAINTAINERS b/MAINTAINERS index b392d34deb62..2f99cd97d628 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17175,6 +17175,13 @@ S: Maintained F: Documentation/devicetree/bindings/leds/backlight/mps,mp3309c.yaml F: drivers/video/backlight/mp3309c.c +MPS MP2869 DRIVER +M: Wensheng Wang +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/mp2869.rst +F: drivers/hwmon/pmbus/mp2869.c + MPS MP2891 DRIVER M: Noah Wang L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index 77add0c6ee53..c5b2c5e95737 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -374,6 +374,15 @@ config SENSORS_MP2856 This driver can also be built as a module. If so, the module will be called mp2856. +config SENSORS_MP2869 + tristate "MPS MP2869" + help + If you say yes here you get hardware monitoring support for MPS + MP2869 Dual Loop Digital Multi-Phase Controller. + + This driver can also be built as a module. If so, the module will + be called mp2869. + config SENSORS_MP2888 tristate "MPS MP2888" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 29cd8a3317d2..6177047414ee 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_SENSORS_MAX31785) += max31785.o obj-$(CONFIG_SENSORS_MAX34440) += max34440.o obj-$(CONFIG_SENSORS_MAX8688) += max8688.o obj-$(CONFIG_SENSORS_MP2856) += mp2856.o +obj-$(CONFIG_SENSORS_MP2869) += mp2869.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2891) += mp2891.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o diff --git a/drivers/hwmon/pmbus/mp2869.c b/drivers/hwmon/pmbus/mp2869.c new file mode 100644 index 000000000000..cc69a1e91dfe --- /dev/null +++ b/drivers/hwmon/pmbus/mp2869.c @@ -0,0 +1,659 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP2869) + */ + +#include +#include +#include +#include +#include "pmbus.h" + +/* + * Vender specific registers, the register MFR_SVI3_IOUT_PRT(0x67), + * READ_PIN_EST(0x94)and READ_IIN_EST(0x95) redefine the standard + * PMBUS register. The MFR_VOUT_LOOP_CTRL(0x29) is used to identify + * the vout scale and the MFR_SVI3_IOUT_PRT(0x67) is used to identify + * the iout scale. The READ_PIN_EST(0x94) is used to read input power + * per rail. The MP2891 does not have standard READ_IIN register(0x89), + * the iin telemetry can be obtained through the vendor redefined + * register READ_IIN_EST(0x95). + */ +#define MFR_SVI3_IOUT_PRT 0x67 +#define MFR_READ_PIN_EST 0x94 +#define MFR_READ_IIN_EST 0x95 +#define MFR_TSNS_FLT_SET 0xBB + +#define MP2869_VIN_OV_FAULT_GAIN 4 +#define MP2869_READ_VOUT_DIV 1024 +#define MP2869_READ_IOUT_DIV 32 +#define MP2869_OVUV_LIMIT_SCALE 10 +#define MP2869_OVUV_DELTA_SCALE 50 +#define MP2869_TEMP_LIMIT_OFFSET 40 +#define MP2869_IOUT_LIMIT_UINT 8 +#define MP2869_POUT_OP_GAIN 2 + +#define MP2869_PAGE_NUM 2 + +#define MP2869_RAIL1_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \ + PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \ + PMBUS_HAVE_IIN | \ + PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_STATUS_TEMP | \ + PMBUS_HAVE_STATUS_INPUT) + +#define MP2869_RAIL2_FUNC (PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | \ + PMBUS_HAVE_POUT | PMBUS_HAVE_TEMP | \ + PMBUS_HAVE_PIN | PMBUS_HAVE_IIN | \ + PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_STATUS_TEMP | \ + PMBUS_HAVE_STATUS_INPUT) + +struct mp2869_data { + struct pmbus_driver_info info; + bool mfr_thwn_flt_en; + int vout_scale[MP2869_PAGE_NUM]; + int iout_scale[MP2869_PAGE_NUM]; +}; + +static const int mp2869_vout_sacle[8] = {6400, 5120, 2560, 2048, 1024, + 4, 2, 1}; +static const int mp2869_iout_sacle[8] = {32, 1, 2, 4, 8, 16, 32, 64}; + +#define to_mp2869_data(x) container_of(x, struct mp2869_data, info) + +static u16 mp2869_reg2data_linear11(u16 word) +{ + s16 exponent; + s32 mantissa; + s64 val; + + exponent = ((s16)word) >> 11; + mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; + val = mantissa; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +static int +mp2869_identify_thwn_flt(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); + if (ret < 0) + return ret; + + data->mfr_thwn_flt_en = FIELD_GET(GENMASK(13, 13), ret); + + return 0; +} + +static int +mp2869_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, PMBUS_VOUT_SCALE_LOOP); + if (ret < 0) + return ret; + + /* + * The output voltage is equal to the READ_VOUT(0x8B) register value multiply + * by vout_scale. + * Obtain vout scale from the register PMBUS_VOUT_SCALE_LOOP, bits 12-10 + * PMBUS_VOUT_SCALE_LOOP[12:10]: + * 000b - 6.25mV/LSB, 001b - 5mV/LSB, 010b - 2.5mV/LSB, 011b - 2mV/LSB + * 100b - 1mV/Lsb, 101b - (1/256)mV/LSB, 110b - (1/512)mV/LSB, + * 111b - (1/1024)mV/LSB + */ + data->vout_scale[page] = mp2869_vout_sacle[FIELD_GET(GENMASK(12, 10), ret)]; + + return 0; +} + +static int +mp2869_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT); + if (ret < 0) + return ret; + + /* + * The output current is equal to the READ_IOUT(0x8C) register value + * multiply by iout_scale. + * Obtain iout_scale from the register MFR_SVI3_IOUT_PRT[2:0]. + * The value is selected as below: + * 000b - 1A/LSB, 001b - (1/32)A/LSB, 010b - (1/16)A/LSB, + * 011b - (1/8)A/LSB, 100b - (1/4)A/LSB, 101b - (1/2)A/LSB + * 110b - 1A/LSB, 111b - 2A/LSB + */ + data->iout_scale[page] = mp2869_iout_sacle[FIELD_GET(GENMASK(2, 0), ret)]; + + return 0; +} + +static int mp2869_read_byte_data(struct i2c_client *client, int page, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + /* + * The calculation of vout in this driver is based on direct format. + * As a result, the format of vout is enforced to direct. + */ + ret = PB_VOUT_MODE_DIRECT; + break; + case PMBUS_STATUS_BYTE: + /* + * If the tsns digital fault is enabled, the TEMPERATURE flag + * of PMBUS_STATUS_BYTE should come from STATUS_MFR_SPECIFIC + * register bit1. + */ + if (!data->mfr_thwn_flt_en) + return -ENODATA; + + ret = pmbus_read_byte_data(client, page, reg); + if (ret < 0) + return ret; + + ret = (ret & ~GENMASK(2, 2)) | + FIELD_PREP(GENMASK(2, 2), + FIELD_GET(GENMASK(1, 1), + pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC))); + break; + case PMBUS_STATUS_TEMPERATURE: + /* + * If the tsns digital fault is enabled, the OT Fault and OT Warning + * flag of PMBUS_STATUS_TEMPERATURE should come from STATUS_MFR_SPECIFIC + * register bit1. + */ + if (!data->mfr_thwn_flt_en) + return -ENODATA; + + ret = pmbus_read_byte_data(client, page, reg); + if (ret < 0) + return ret; + + ret = (ret & ~GENMASK(7, 6)) | + FIELD_PREP(GENMASK(6, 6), + FIELD_GET(GENMASK(1, 1), + pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC))) | + FIELD_PREP(GENMASK(7, 7), + FIELD_GET(GENMASK(1, 1), + pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC))); + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int mp2869_read_word_data(struct i2c_client *client, int page, int phase, + int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + switch (reg) { + case PMBUS_STATUS_WORD: + /* + * If the tsns digital fault is enabled, the OT Fault flag + * of PMBUS_STATUS_WORD should come from STATUS_MFR_SPECIFIC + * register bit1. + */ + if (!data->mfr_thwn_flt_en) + return -ENODATA; + + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = (ret & ~GENMASK(2, 2)) | + FIELD_PREP(GENMASK(2, 2), + FIELD_GET(GENMASK(1, 1), + pmbus_read_byte_data(client, page, + PMBUS_STATUS_MFR_SPECIFIC))); + break; + case PMBUS_READ_VIN: + /* + * The MP2869 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is + * 31.25mV/LSB. And the vin scale is set to 31.25mV/Lsb(using r/m/b scale) + * in MP2869 pmbus_driver_info struct, so the word data bit0-bit10 can be + * returned to pmbus core directly. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(10, 0), ret); + break; + case PMBUS_READ_IIN: + /* + * The MP2869 redefine the standard 0x95 register as iin telemetry + * per rail. + */ + ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST); + if (ret < 0) + return ret; + + break; + case PMBUS_READ_PIN: + /* + * The MP2869 redefine the standard 0x94 register as pin telemetry + * per rail. The MP2869 MFR_READ_PIN_EST register is linear11 format, + * but the pin scale is set to 1W/Lsb(using r/m/b scale). As a result, + * the pin read from MP2869 should be converted to W, then return + * the result to pmbus core. + */ + ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST); + if (ret < 0) + return ret; + + ret = mp2869_reg2data_linear11(ret); + break; + case PMBUS_READ_VOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * data->vout_scale[page], + MP2869_READ_VOUT_DIV); + break; + case PMBUS_READ_IOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale[page], + MP2869_READ_IOUT_DIV); + break; + case PMBUS_READ_POUT: + /* + * The MP2869 PMBUS_READ_POUT register is linear11 format, but the pout + * scale is set to 1W/Lsb(using r/m/b scale). As a result, the pout read + * from MP2869 should be converted to W, then return the result to pmbus + * core. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = mp2869_reg2data_linear11(ret); + break; + case PMBUS_READ_TEMPERATURE_1: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(10, 0), ret); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + if (FIELD_GET(GENMASK(12, 9), ret)) + ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE + + (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE; + else + ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE; + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + if (FIELD_GET(GENMASK(12, 9), ret)) + ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE - + (FIELD_GET(GENMASK(12, 9), ret) + 1) * MP2869_OVUV_DELTA_SCALE; + else + ret = FIELD_GET(GENMASK(8, 0), ret) * MP2869_OVUV_LIMIT_SCALE; + break; + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + /* + * The scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT + * is 1°C/LSB and they have 40°C offset. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = (ret & GENMASK(7, 0)) - MP2869_TEMP_LIMIT_OFFSET; + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = (ret & GENMASK(7, 0)) * MP2869_VIN_OV_FAULT_GAIN; + break; + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(9, 0), ret); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * data->iout_scale[page] * + MP2869_IOUT_LIMIT_UINT, MP2869_READ_IOUT_DIV); + break; + case PMBUS_POUT_OP_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = (ret & GENMASK(7, 0)) * MP2869_POUT_OP_GAIN; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mp2869_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp2869_data *data = to_mp2869_data(info); + int ret; + + switch (reg) { + case PMBUS_VOUT_UV_FAULT_LIMIT: + /* + * The MP2869 PMBUS_VOUT_UV_FAULT_LIMIT[8:0] is the limit value, + * and bit9-bit15 should not be changed. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + if (FIELD_GET(GENMASK(12, 9), ret)) + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(8, 0)) | + FIELD_PREP(GENMASK(8, 0), + DIV_ROUND_CLOSEST(word + + (FIELD_GET(GENMASK(12, 9), + ret) + 1) * + MP2869_OVUV_DELTA_SCALE, + MP2869_OVUV_LIMIT_SCALE))); + else + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(8, 0)) | + FIELD_PREP(GENMASK(8, 0), + DIV_ROUND_CLOSEST(word, + MP2869_OVUV_LIMIT_SCALE))); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + /* + * The MP2869 PMBUS_VOUT_OV_FAULT_LIMIT[8:0] is the limit value, + * and bit9-bit15 should not be changed. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + if (FIELD_GET(GENMASK(12, 9), ret)) + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(8, 0)) | + FIELD_PREP(GENMASK(8, 0), + DIV_ROUND_CLOSEST(word - + (FIELD_GET(GENMASK(12, 9), + ret) + 1) * + MP2869_OVUV_DELTA_SCALE, + MP2869_OVUV_LIMIT_SCALE))); + else + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(8, 0)) | + FIELD_PREP(GENMASK(8, 0), + DIV_ROUND_CLOSEST(word, + MP2869_OVUV_LIMIT_SCALE))); + break; + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + /* + * If the tsns digital fault is enabled, the PMBUS_OT_FAULT_LIMIT and + * PMBUS_OT_WARN_LIMIT can not be written. + */ + if (data->mfr_thwn_flt_en) + return -EINVAL; + + /* + * The MP2869 scale of MP2869 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT + * have 40°C offset. The bit0-bit7 is the limit value, and bit8-bit15 + * should not be changed. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(7, 0)) | + FIELD_PREP(GENMASK(7, 0), + word + MP2869_TEMP_LIMIT_OFFSET)); + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + /* + * The MP2869 PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, and bit8-bit15 + * should not be changed. The scale of PMBUS_VIN_OV_FAULT_LIMIT is 125mV/Lsb, + * but the vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data + * should divide by MP2869_VIN_OV_FAULT_GAIN(4) + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(7, 0)) | + FIELD_PREP(GENMASK(7, 0), + DIV_ROUND_CLOSEST(word, + MP2869_VIN_OV_FAULT_GAIN))); + break; + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + /* + * The PMBUS_VIN_UV_LIMIT[9:0] is the limit value, and bit10-bit15 should + * not be changed. The scale of PMBUS_VIN_UV_LIMIT is 31.25mV/Lsb, and the + * vin scale is set to 31.25mV/Lsb(using r/m/b scale), so the word data can + * be written directly. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(9, 0)) | + FIELD_PREP(GENMASK(9, 0), + word)); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_write_word_data(client, page, reg, + DIV_ROUND_CLOSEST(word * MP2869_READ_IOUT_DIV, + MP2869_IOUT_LIMIT_UINT * + data->iout_scale[page])); + break; + case PMBUS_POUT_OP_WARN_LIMIT: + /* + * The POUT_OP_WARN_LIMIT[11:0] is the limit value, and bit12-bit15 should + * not be changed. The scale of POUT_OP_WARN_LIMIT is 2W/Lsb. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(11, 0)) | + FIELD_PREP(GENMASK(11, 0), + DIV_ROUND_CLOSEST(word, + MP2869_POUT_OP_GAIN))); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mp2869_identify(struct i2c_client *client, struct pmbus_driver_info *info) +{ + int ret; + + /* Identify whether tsns digital fault is enable */ + ret = mp2869_identify_thwn_flt(client, info, 1); + if (ret < 0) + return 0; + + /* Identify vout scale for rail1. */ + ret = mp2869_identify_vout_scale(client, info, 0); + if (ret < 0) + return ret; + + /* Identify vout scale for rail2. */ + ret = mp2869_identify_vout_scale(client, info, 1); + if (ret < 0) + return ret; + + /* Identify iout scale for rail 1. */ + ret = mp2869_identify_iout_scale(client, info, 0); + if (ret < 0) + return ret; + + /* Identify iout scale for rail 2. */ + return mp2869_identify_iout_scale(client, info, 1); +} + +static const struct pmbus_driver_info mp2869_info = { + .pages = MP2869_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_CURRENT_IN] = linear, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_POWER] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + + .m[PSC_VOLTAGE_IN] = 32, + .R[PSC_VOLTAGE_IN] = 0, + .b[PSC_VOLTAGE_IN] = 0, + + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .b[PSC_VOLTAGE_OUT] = 0, + + .m[PSC_CURRENT_OUT] = 1, + .R[PSC_CURRENT_OUT] = 0, + .b[PSC_CURRENT_OUT] = 0, + + .m[PSC_TEMPERATURE] = 1, + .R[PSC_TEMPERATURE] = 0, + .b[PSC_TEMPERATURE] = 0, + + .m[PSC_POWER] = 1, + .R[PSC_POWER] = 0, + .b[PSC_POWER] = 0, + + .func[0] = MP2869_RAIL1_FUNC, + .func[1] = MP2869_RAIL2_FUNC, + .read_word_data = mp2869_read_word_data, + .write_word_data = mp2869_write_word_data, + .read_byte_data = mp2869_read_byte_data, + .identify = mp2869_identify, +}; + +static int mp2869_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp2869_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct mp2869_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mp2869_info, sizeof(*info)); + info = &data->info; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id mp2869_id[] = { + {"mp2869", 0}, + {"mp29608", 1}, + {"mp29612", 2}, + {"mp29816", 3}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mp2869_id); + +static const struct of_device_id __maybe_unused mp2869_of_match[] = { + {.compatible = "mps,mp2869", .data = (void *)0}, + {.compatible = "mps,mp29608", .data = (void *)1}, + {.compatible = "mps,mp29612", .data = (void *)2}, + {.compatible = "mps,mp29816", .data = (void *)3}, + {} +}; +MODULE_DEVICE_TABLE(of, mp2869_of_match); + +static struct i2c_driver mp2869_driver = { + .driver = { + .name = "mp2869", + .of_match_table = mp2869_of_match, + }, + .probe = mp2869_probe, + .id_table = mp2869_id, +}; + +module_i2c_driver(mp2869_driver); + +MODULE_AUTHOR("Wensheng Wang "); +MODULE_DESCRIPTION("PMBus driver for MPS MP2869"); +MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("PMBUS"); -- cgit v1.2.3 From 90bad684e9ac5ae435c2715fab36f6799849e800 Mon Sep 17 00:00:00 2001 From: Wensheng Wang Date: Tue, 5 Aug 2025 18:20:20 +0800 Subject: hwmon: add MP29502 driver Add support for MPS VR controller mp29502. This driver exposes telemetry and limits value readings and writtings. Signed-off-by: Wensheng Wang Link: https://lore.kernel.org/r/20250805102020.749850-3-wenswang@yeah.net [groeck: Fixed document formatting] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/mp29502.rst | 93 ++++++ MAINTAINERS | 7 + drivers/hwmon/pmbus/Kconfig | 9 + drivers/hwmon/pmbus/Makefile | 1 + drivers/hwmon/pmbus/mp29502.c | 670 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 781 insertions(+) create mode 100644 Documentation/hwmon/mp29502.rst create mode 100644 drivers/hwmon/pmbus/mp29502.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 36303148dc43..b4d26a6fa3a5 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -176,6 +176,7 @@ Hardware Monitoring Kernel Drivers mp2869 mp2888 mp2891 + mp29502 mp2975 mp2993 mp5023 diff --git a/Documentation/hwmon/mp29502.rst b/Documentation/hwmon/mp29502.rst new file mode 100644 index 000000000000..893e741a6b71 --- /dev/null +++ b/Documentation/hwmon/mp29502.rst @@ -0,0 +1,93 @@ +.. SPDX-License-Identifier: GPL-2.0 + +Kernel driver mp29502 +===================== + +Supported chips: + + * MPS mp29502 + + Prefix: 'mp29502' + +Author: + + Wensheng Wang + +Description +----------- + +This driver implements support for Monolithic Power Systems, Inc. (MPS) +MP29502 Digital Multi-phase Controller. + +Device compliant with: + +- PMBus rev 1.3 interface. + +The driver exports the following attributes via the 'sysfs' files +for input voltage: + +**in1_input** + +**in1_label** + +**in1_crit** + +**in1_crit_alarm** + +The driver provides the following attributes for output voltage: + +**in2_input** + +**in2_label** + +**in2_crit** + +**in2_crit_alarm** + +**in2_lcrit** + +**in2_lcrit_alarm** + +The driver provides the following attributes for input current: + +**curr1_input** + +**curr1_label** + +The driver provides the following attributes for output current: + +**curr2_input** + +**curr2_label** + +**curr2_crit** + +**curr2_crit_alarm** + +**curr2_max** + +**curr2_max_alarm** + +The driver provides the following attributes for input power: + +**power1_input** + +**power1_label** + +The driver provides the following attributes for output power: + +**power2_input** + +**power2_label** + +The driver provides the following attributes for temperature: + +**temp1_input** + +**temp1_crit** + +**temp1_crit_alarm** + +**temp1_max** + +**temp1_max_alarm** diff --git a/MAINTAINERS b/MAINTAINERS index 2f99cd97d628..4e6f41ed4f2c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17189,6 +17189,13 @@ S: Maintained F: Documentation/hwmon/mp2891.rst F: drivers/hwmon/pmbus/mp2891.c +MPS MP29502 DRIVER +M: Wensheng Wang +L: linux-hwmon@vger.kernel.org +S: Maintained +F: Documentation/hwmon/mp29502.rst +F: drivers/hwmon/pmbus/mp29502.c + MPS MP2993 DRIVER M: Noah Wang L: linux-hwmon@vger.kernel.org diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig index c5b2c5e95737..da04ff6df28b 100644 --- a/drivers/hwmon/pmbus/Kconfig +++ b/drivers/hwmon/pmbus/Kconfig @@ -401,6 +401,15 @@ config SENSORS_MP2891 This driver can also be built as a module. If so, the module will be called mp2891. +config SENSORS_MP29502 + tristate "MPS MP29502" + help + If you say yes here you get hardware monitoring support for MPS + MP29502 Dual Loop Digital Multi-Phase Controller. + + This driver can also be built as a module. If so, the module will + be called mp29502. + config SENSORS_MP2975 tristate "MPS MP2975" help diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile index 6177047414ee..4c5ff3f32c5e 100644 --- a/drivers/hwmon/pmbus/Makefile +++ b/drivers/hwmon/pmbus/Makefile @@ -40,6 +40,7 @@ obj-$(CONFIG_SENSORS_MP2856) += mp2856.o obj-$(CONFIG_SENSORS_MP2869) += mp2869.o obj-$(CONFIG_SENSORS_MP2888) += mp2888.o obj-$(CONFIG_SENSORS_MP2891) += mp2891.o +obj-$(CONFIG_SENSORS_MP29502) += mp29502.o obj-$(CONFIG_SENSORS_MP2975) += mp2975.o obj-$(CONFIG_SENSORS_MP2993) += mp2993.o obj-$(CONFIG_SENSORS_MP5023) += mp5023.o diff --git a/drivers/hwmon/pmbus/mp29502.c b/drivers/hwmon/pmbus/mp29502.c new file mode 100644 index 000000000000..7241373f1557 --- /dev/null +++ b/drivers/hwmon/pmbus/mp29502.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Hardware monitoring driver for MPS Multi-phase Digital VR Controllers(MP29502) + */ + +#include +#include +#include +#include +#include "pmbus.h" + +#define MFR_VOUT_SCALE_LOOP 0x29 +#define MFR_SVI3_IOUT_PRT 0x67 +#define MFR_READ_PIN_EST 0x94 +#define MFR_READ_IIN_EST 0x95 +#define MFR_VOUT_PROT1 0x3D +#define MFR_VOUT_PROT2 0x51 +#define MFR_SLOPE_CNT_SET 0xA8 +#define MFR_TSNS_FLT_SET 0xBB + +#define MP29502_VIN_OV_GAIN 4 +#define MP29502_TEMP_LIMIT_OFFSET 40 +#define MP29502_READ_VOUT_DIV 1024 +#define MP29502_READ_IOUT_DIV 32 +#define MP29502_IOUT_LIMIT_UINT 8 +#define MP29502_OVUV_LIMIT_SCALE 10 +#define MP28502_VOUT_OV_GAIN 512 +#define MP28502_VOUT_OV_SCALE 40 +#define MP29502_VOUT_UV_OFFSET 36 +#define MP29502_PIN_GAIN 2 +#define MP29502_IIN_DIV 2 + +#define MP29502_PAGE_NUM 1 + +#define MP29502_RAIL_FUNC (PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | \ + PMBUS_HAVE_IOUT | PMBUS_HAVE_POUT | \ + PMBUS_HAVE_TEMP | PMBUS_HAVE_PIN | \ + PMBUS_HAVE_IIN | \ + PMBUS_HAVE_STATUS_VOUT | \ + PMBUS_HAVE_STATUS_IOUT | \ + PMBUS_HAVE_STATUS_TEMP | \ + PMBUS_HAVE_STATUS_INPUT) + +struct mp29502_data { + struct pmbus_driver_info info; + int vout_scale; + int vout_bottom_div; + int vout_top_div; + int ovp_div; + int iout_scale; +}; + +#define to_mp29502_data(x) container_of(x, struct mp29502_data, info) + +static u16 mp29502_reg2data_linear11(u16 word) +{ + s16 exponent; + s32 mantissa; + s64 val; + + exponent = ((s16)word) >> 11; + mantissa = ((s16)((word & 0x7ff) << 5)) >> 5; + val = mantissa; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return val; +} + +static int +mp29502_identify_vout_scale(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_VOUT_SCALE_LOOP); + if (ret < 0) + return ret; + + switch (FIELD_GET(GENMASK(12, 10), ret)) { + case 0: + data->vout_scale = 6400; + break; + case 1: + data->vout_scale = 5120; + break; + case 2: + data->vout_scale = 2560; + break; + case 3: + data->vout_scale = 2048; + break; + case 4: + data->vout_scale = 1024; + break; + case 5: + data->vout_scale = 4; + break; + case 6: + data->vout_scale = 2; + break; + case 7: + data->vout_scale = 1; + break; + default: + data->vout_scale = 1; + break; + } + + return 0; +} + +static int +mp29502_identify_vout_divider(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT1); + if (ret < 0) + return ret; + + data->vout_bottom_div = FIELD_GET(GENMASK(11, 0), ret); + + ret = i2c_smbus_read_word_data(client, MFR_VOUT_PROT2); + if (ret < 0) + return ret; + + data->vout_top_div = FIELD_GET(GENMASK(14, 0), ret); + + return 0; +} + +static int +mp29502_identify_ovp_divider(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_SLOPE_CNT_SET); + if (ret < 0) + return ret; + + data->ovp_div = FIELD_GET(GENMASK(9, 0), ret); + + return 0; +} + +static int +mp29502_identify_iout_scale(struct i2c_client *client, struct pmbus_driver_info *info, + int page) +{ + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_SVI3_IOUT_PRT); + if (ret < 0) + return ret; + + switch (ret & GENMASK(2, 0)) { + case 0: + case 6: + data->iout_scale = 32; + break; + case 1: + data->iout_scale = 1; + break; + case 2: + data->iout_scale = 2; + break; + case 3: + data->iout_scale = 4; + break; + case 4: + data->iout_scale = 8; + break; + case 5: + data->iout_scale = 16; + break; + default: + data->iout_scale = 64; + break; + } + + return 0; +} + +static int mp29502_read_vout_ov_limit(struct i2c_client *client, struct mp29502_data *data) +{ + int ret; + int ov_value; + + /* + * This is because the vout ov fault limit value comes from + * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit + * value comes from page0 reg. So the page should be set to + * 0 after the reading of vout ov limit. + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); + if (ret < 0) + return ret; + + ov_value = DIV_ROUND_CLOSEST(FIELD_GET(GENMASK(12, 7), ret) * + MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE, + data->ovp_div); + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + return ov_value; +} + +static int mp29502_write_vout_ov_limit(struct i2c_client *client, u16 word, + struct mp29502_data *data) +{ + int ret; + + /* + * This is because the vout ov fault limit value comes from + * page1 MFR_TSNS_FLT_SET reg, and other telemetry and limit + * value comes from page0 reg. So the page should be set to + * 0 after the writing of vout ov limit. + */ + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 1); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_word_data(client, MFR_TSNS_FLT_SET); + if (ret < 0) + return ret; + + ret = i2c_smbus_write_word_data(client, MFR_TSNS_FLT_SET, + (ret & ~GENMASK(12, 7)) | + FIELD_PREP(GENMASK(12, 7), + DIV_ROUND_CLOSEST(word * data->ovp_div, + MP28502_VOUT_OV_GAIN * MP28502_VOUT_OV_SCALE))); + + return i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); +} + +static int mp29502_read_byte_data(struct i2c_client *client, int page, int reg) +{ + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VOUT_MODE: + ret = PB_VOUT_MODE_DIRECT; + break; + default: + ret = -ENODATA; + break; + } + + return ret; +} + +static int mp29502_read_word_data(struct i2c_client *client, int page, + int phase, int reg) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + switch (reg) { + case PMBUS_STATUS_WORD: + ret = -ENODATA; + break; + case PMBUS_READ_VIN: + /* + * The MP29502 PMBUS_READ_VIN[10:0] is the vin value, the vin scale is + * 125mV/LSB. And the vin scale is set to 125mV/Lsb(using r/m/b scale) + * in MP29502 pmbus_driver_info struct, so the word data bit0-bit10 can + * be returned to pmbus core directly. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(10, 0), ret); + break; + case PMBUS_READ_VOUT: + /* + * The MP29502 PMBUS_READ_VOUT[11:0] is the vout value, and vout + * value is calculated based on vout scale and vout divider. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(11, 0)) * + data->vout_scale * + (data->vout_bottom_div + + 4 * data->vout_top_div), + MP29502_READ_VOUT_DIV * + data->vout_bottom_div); + break; + case PMBUS_READ_IIN: + /* + * The MP29502 MFR_READ_IIN_EST register is linear11 format, and the + * exponent is not a constant value. But the iin scale is set to + * 1A/Lsb(using r/m/b scale). As a result, the iin read from MP29502 + * should be calculated to A, then return the result to pmbus core. + */ + ret = pmbus_read_word_data(client, page, phase, MFR_READ_IIN_EST); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret), + MP29502_IIN_DIV); + break; + case PMBUS_READ_PIN: + /* + * The MP29502 MFR_READ_PIN_EST register is linear11 format, and the + * exponent is not a constant value. But the pin scale is set to + * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502 + * should be calculated to W, then return the result to pmbus core. + */ + ret = pmbus_read_word_data(client, page, phase, MFR_READ_PIN_EST); + if (ret < 0) + return ret; + + ret = mp29502_reg2data_linear11(ret) * MP29502_PIN_GAIN; + break; + case PMBUS_READ_POUT: + /* + * The MP29502 PMBUS_READ_POUT register is linear11 format, and the + * exponent is not a constant value. But the pout scale is set to + * 1W/Lsb(using r/m/b scale). As a result, the pout read from MP29502 + * should be calculated to W, then return the result to pmbus core. + * And the pout is calculated based on vout divider. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST(mp29502_reg2data_linear11(ret) * + (data->vout_bottom_div + + 4 * data->vout_top_div), + data->vout_bottom_div); + break; + case PMBUS_READ_IOUT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(10, 0)) * data->iout_scale, + MP29502_READ_IOUT_DIV); + break; + case PMBUS_READ_TEMPERATURE_1: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(10, 0), ret); + break; + case PMBUS_VIN_OV_FAULT_LIMIT: + /* + * The MP29502 PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but + * the vin scale is set to 125mV/Lsb(using r/m/b scale), + * so the word data should multiply by 4. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(7, 0), ret) * MP29502_VIN_OV_GAIN; + break; + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + /* + * The MP29502 PMBUS_VIN_UV_WARN_LIMIT and PMBUS_VIN_UV_FAULT_LIMIT + * scale is 125mV/Lsb, but the vin scale is set to 125mV/Lsb(using + * r/m/b scale), so the word data bit0-bit9 can be returned to pmbus + * core directly. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = FIELD_GET(GENMASK(9, 0), ret); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + /* + * The MP29502 vout ov fault limit value comes from + * page1 MFR_TSNS_FLT_SET[12:7]. + */ + ret = mp29502_read_vout_ov_limit(client, data); + if (ret < 0) + return ret; + + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((FIELD_GET(GENMASK(8, 0), ret) * + MP29502_OVUV_LIMIT_SCALE - + MP29502_VOUT_UV_OFFSET) * + (data->vout_bottom_div + + 4 * data->vout_top_div), + data->vout_bottom_div); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = DIV_ROUND_CLOSEST((ret & GENMASK(7, 0)) * + data->iout_scale * + MP29502_IOUT_LIMIT_UINT, + MP29502_READ_IOUT_DIV); + break; + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + /* + * The scale of MP29502 PMBUS_OT_FAULT_LIMIT and PMBUS_OT_WARN_LIMIT + * is 1°C/LSB and they have 40°C offset. + */ + ret = pmbus_read_word_data(client, page, phase, reg); + if (ret < 0) + return ret; + + ret = (ret & GENMASK(7, 0)) - MP29502_TEMP_LIMIT_OFFSET; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mp29502_write_word_data(struct i2c_client *client, int page, int reg, + u16 word) +{ + const struct pmbus_driver_info *info = pmbus_get_driver_info(client); + struct mp29502_data *data = to_mp29502_data(info); + int ret; + + ret = i2c_smbus_write_byte_data(client, PMBUS_PAGE, 0); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_VIN_OV_FAULT_LIMIT: + /* + * The PMBUS_VIN_OV_FAULT_LIMIT[7:0] is the limit value, + * and bit8-bit15 should not be changed. The scale of + * PMBUS_VIN_OV_FAULT_LIMIT is 500mV/Lsb, but the vin + * scale is set to 125mV/Lsb(using r/m/b scale), so + * the word data should divide by 4. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(7, 0)) | + FIELD_PREP(GENMASK(7, 0), + DIV_ROUND_CLOSEST(word, + MP29502_VIN_OV_GAIN))); + break; + case PMBUS_VIN_UV_WARN_LIMIT: + case PMBUS_VIN_UV_FAULT_LIMIT: + /* + * The PMBUS_VIN_UV_WARN_LIMIT[9:0] and PMBUS_VIN_UV_FAULT_LIMIT[9:0] + * are the limit value, and bit10-bit15 should not be changed. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(9, 0)) | + FIELD_PREP(GENMASK(9, 0), + word)); + break; + case PMBUS_VOUT_OV_FAULT_LIMIT: + ret = mp29502_write_vout_ov_limit(client, word, data); + if (ret < 0) + return ret; + + break; + case PMBUS_VOUT_UV_FAULT_LIMIT: + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(8, 0)) | + FIELD_PREP(GENMASK(8, 0), + DIV_ROUND_CLOSEST(word * + data->vout_bottom_div + + MP29502_VOUT_UV_OFFSET * + (data->vout_bottom_div + + 4 * data->vout_top_div), + MP29502_OVUV_LIMIT_SCALE * + (data->vout_bottom_div + + 4 * data->vout_top_div)))); + break; + case PMBUS_IOUT_OC_FAULT_LIMIT: + case PMBUS_IOUT_OC_WARN_LIMIT: + ret = pmbus_write_word_data(client, page, reg, + DIV_ROUND_CLOSEST(word * + MP29502_READ_IOUT_DIV, + MP29502_IOUT_LIMIT_UINT * + data->iout_scale)); + break; + case PMBUS_OT_FAULT_LIMIT: + case PMBUS_OT_WARN_LIMIT: + /* + * The PMBUS_OT_FAULT_LIMIT[7:0] and PMBUS_OT_WARN_LIMIT[7:0] + * are the limit value, and bit8-bit15 should not be changed. + */ + ret = pmbus_read_word_data(client, page, 0xff, reg); + if (ret < 0) + return ret; + + ret = pmbus_write_word_data(client, page, reg, + (ret & ~GENMASK(7, 0)) | + FIELD_PREP(GENMASK(7, 0), + word + MP29502_TEMP_LIMIT_OFFSET)); + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static int mp29502_identify(struct i2c_client *client, struct pmbus_driver_info *info) +{ + int ret; + + /* Identify vout scale */ + ret = mp29502_identify_vout_scale(client, info, 0); + if (ret < 0) + return ret; + + /* Identify vout divider. */ + ret = mp29502_identify_vout_divider(client, info, 1); + if (ret < 0) + return ret; + + /* Identify ovp divider. */ + ret = mp29502_identify_ovp_divider(client, info, 1); + if (ret < 0) + return ret; + + /* Identify iout scale */ + return mp29502_identify_iout_scale(client, info, 0); +} + +static const struct pmbus_driver_info mp29502_info = { + .pages = MP29502_PAGE_NUM, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_TEMPERATURE] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_POWER] = direct, + + .m[PSC_VOLTAGE_IN] = 8, + .R[PSC_VOLTAGE_IN] = 0, + .b[PSC_VOLTAGE_IN] = 0, + + .m[PSC_VOLTAGE_OUT] = 1, + .R[PSC_VOLTAGE_OUT] = 3, + .b[PSC_VOLTAGE_OUT] = 0, + + .m[PSC_TEMPERATURE] = 1, + .R[PSC_TEMPERATURE] = 0, + .b[PSC_TEMPERATURE] = 0, + + .m[PSC_CURRENT_IN] = 1, + .R[PSC_CURRENT_IN] = 0, + .b[PSC_CURRENT_IN] = 0, + + .m[PSC_CURRENT_OUT] = 1, + .R[PSC_CURRENT_OUT] = 0, + .b[PSC_CURRENT_OUT] = 0, + + .m[PSC_POWER] = 1, + .R[PSC_POWER] = 0, + .b[PSC_POWER] = 0, + + .func[0] = MP29502_RAIL_FUNC, + .read_word_data = mp29502_read_word_data, + .read_byte_data = mp29502_read_byte_data, + .write_word_data = mp29502_write_word_data, + .identify = mp29502_identify, +}; + +static int mp29502_probe(struct i2c_client *client) +{ + struct pmbus_driver_info *info; + struct mp29502_data *data; + + data = devm_kzalloc(&client->dev, sizeof(struct mp29502_data), + GFP_KERNEL); + if (!data) + return -ENOMEM; + + memcpy(&data->info, &mp29502_info, sizeof(*info)); + info = &data->info; + + return pmbus_do_probe(client, info); +} + +static const struct i2c_device_id mp29502_id[] = { + {"mp29502", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, mp29502_id); + +static const struct of_device_id __maybe_unused mp29502_of_match[] = { + {.compatible = "mps,mp29502"}, + {} +}; +MODULE_DEVICE_TABLE(of, mp29502_of_match); + +static struct i2c_driver mp29502_driver = { + .driver = { + .name = "mp29502", + .of_match_table = mp29502_of_match, + }, + .probe = mp29502_probe, + .id_table = mp29502_id, +}; + +module_i2c_driver(mp29502_driver); + +MODULE_AUTHOR("Wensheng Wang Date: Thu, 5 Jun 2025 16:23:57 -0700 Subject: hwmon: Serialize accesses in hwmon core Implement locking in the hardware monitoring core for drivers using the _with_info() API functions. Most hardware monitoring drivers need to support locking to protect against parallel accesses from userspace. With older API functions, such locking had to be implemented in the driver code since sysfs attributes were created by the driver. However, the _with_info() API creates sysfs attributes in the hardware monitoring core. This makes it easy to move the locking primitives into that code. This has the benefit of simplifying driver code while at the same time reducing the risk of incomplete of bad locking implementations in hardware monitoring drivers. While this means that all accesses are forced to be synchronized, this has little if any practical impact since accesses are expected to be low frequency and are typically synchronized from userspace anyway since only a single process is accessing the data. On top of that, many drivers use regmap, which also has its own locking scheme and already serializes accesses. Signed-off-by: Guenter Roeck --- Documentation/hwmon/hwmon-kernel-api.rst | 10 ++++++++ drivers/hwmon/hwmon.c | 42 ++++++++++++++++++++++++++------ include/linux/hwmon.h | 3 +++ 3 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Documentation/hwmon/hwmon-kernel-api.rst b/Documentation/hwmon/hwmon-kernel-api.rst index 037b69c23cb5..1d7f1397a827 100644 --- a/Documentation/hwmon/hwmon-kernel-api.rst +++ b/Documentation/hwmon/hwmon-kernel-api.rst @@ -42,6 +42,9 @@ register/unregister functions:: char *devm_hwmon_sanitize_name(struct device *dev, const char *name); + void hwmon_lock(struct device *dev); + void hwmon_unlock(struct device *dev); + hwmon_device_register_with_info registers 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 @@ -79,6 +82,13 @@ devm_hwmon_sanitize_name is the resource managed version of hwmon_sanitize_name; the memory will be freed automatically on device removal. +When using ``[devm_]hwmon_device_register_with_info()`` to register the +hardware monitoring device, accesses using the associated access functions +are serialised by the hardware monitoring core. If a driver needs locking +for other functions such as interrupt handlers or for attributes which are +fully implemented in the driver, hwmon_lock() and hwmon_unlock() can be used +to ensure that calls to those functions are serialized. + Using devm_hwmon_device_register_with_info() -------------------------------------------- diff --git a/drivers/hwmon/hwmon.c b/drivers/hwmon/hwmon.c index 2e17f3a4c59b..0b4bdcd33c7b 100644 --- a/drivers/hwmon/hwmon.c +++ b/drivers/hwmon/hwmon.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,7 @@ struct hwmon_device { const char *label; struct device dev; const struct hwmon_chip_info *chip; + struct mutex lock; struct list_head tzdata; struct attribute_group group; const struct attribute_group **groups; @@ -165,6 +167,8 @@ static int hwmon_thermal_get_temp(struct thermal_zone_device *tz, int *temp) int ret; long t; + guard(mutex)(&hwdev->lock); + ret = hwdev->chip->ops->read(tdata->dev, hwmon_temp, hwmon_temp_input, tdata->index, &t); if (ret < 0) @@ -193,6 +197,8 @@ static int hwmon_thermal_set_trips(struct thermal_zone_device *tz, int low, int if (!info[i]) return 0; + guard(mutex)(&hwdev->lock); + if (info[i]->config[tdata->index] & HWMON_T_MIN) { err = chip->ops->write(tdata->dev, hwmon_temp, hwmon_temp_min, tdata->index, low); @@ -330,8 +336,6 @@ static int hwmon_attr_base(enum hwmon_sensor_types type) * attached to an i2c client device. */ -static DEFINE_MUTEX(hwmon_pec_mutex); - static int hwmon_match_device(struct device *dev, const void *data) { return dev->class == &hwmon_class; @@ -362,17 +366,16 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, if (!hdev) return -ENODEV; - mutex_lock(&hwmon_pec_mutex); - /* * If there is no write function, we assume that chip specific * handling is not required. */ hwdev = to_hwmon_device(hdev); + guard(mutex)(&hwdev->lock); if (hwdev->chip->ops->write) { err = hwdev->chip->ops->write(hdev, hwmon_chip, hwmon_chip_pec, 0, val); if (err && err != -EOPNOTSUPP) - goto unlock; + goto put; } if (!val) @@ -381,8 +384,7 @@ static ssize_t pec_store(struct device *dev, struct device_attribute *devattr, client->flags |= I2C_CLIENT_PEC; err = count; -unlock: - mutex_unlock(&hwmon_pec_mutex); +put: put_device(hdev); return err; @@ -426,10 +428,13 @@ static ssize_t hwmon_attr_show(struct device *dev, struct device_attribute *devattr, char *buf) { struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); s64 val64; long val; int ret; + guard(mutex)(&hwdev->lock); + ret = hattr->ops->read(dev, hattr->type, hattr->attr, hattr->index, (hattr->type == hwmon_energy64) ? (long *)&val64 : &val); if (ret < 0) @@ -449,10 +454,13 @@ static ssize_t hwmon_attr_show_string(struct device *dev, char *buf) { struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); enum hwmon_sensor_types type = hattr->type; const char *s; int ret; + guard(mutex)(&hwdev->lock); + ret = hattr->ops->read_string(dev, hattr->type, hattr->attr, hattr->index, &s); if (ret < 0) @@ -469,6 +477,7 @@ static ssize_t hwmon_attr_store(struct device *dev, const char *buf, size_t count) { struct hwmon_device_attribute *hattr = to_hwmon_attr(devattr); + struct hwmon_device *hwdev = to_hwmon_device(dev); long val; int ret; @@ -476,6 +485,8 @@ static ssize_t hwmon_attr_store(struct device *dev, if (ret < 0) return ret; + guard(mutex)(&hwdev->lock); + ret = hattr->ops->write(dev, hattr->type, hattr->attr, hattr->index, val); if (ret < 0) @@ -791,6 +802,22 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, } EXPORT_SYMBOL_GPL(hwmon_notify_event); +void hwmon_lock(struct device *dev) +{ + struct hwmon_device *hwdev = to_hwmon_device(dev); + + mutex_lock(&hwdev->lock); +} +EXPORT_SYMBOL_GPL(hwmon_lock); + +void hwmon_unlock(struct device *dev) +{ + struct hwmon_device *hwdev = to_hwmon_device(dev); + + mutex_unlock(&hwdev->lock); +} +EXPORT_SYMBOL_GPL(hwmon_unlock); + static int hwmon_num_channel_attrs(const struct hwmon_channel_info *info) { int i, n; @@ -951,6 +978,7 @@ __hwmon_device_register(struct device *dev, const char *name, void *drvdata, tdev = tdev->parent; hdev->of_node = tdev ? tdev->of_node : NULL; hwdev->chip = chip; + mutex_init(&hwdev->lock); dev_set_drvdata(hdev, drvdata); dev_set_name(hdev, HWMON_ID_FORMAT, id); err = device_register(hdev); diff --git a/include/linux/hwmon.h b/include/linux/hwmon.h index 886fc90b2d25..301a83afbd66 100644 --- a/include/linux/hwmon.h +++ b/include/linux/hwmon.h @@ -492,6 +492,9 @@ int hwmon_notify_event(struct device *dev, enum hwmon_sensor_types type, char *hwmon_sanitize_name(const char *name); char *devm_hwmon_sanitize_name(struct device *dev, const char *name); +void hwmon_lock(struct device *dev); +void hwmon_unlock(struct device *dev); + /** * hwmon_is_bad_char - Is the char invalid in a hwmon name * @ch: the char to be considered -- cgit v1.2.3 From 94a28f9ba264b4b93cd1efa97992db59d74e8048 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Mon, 8 Sep 2025 10:54:49 -0500 Subject: hwmon: (sht21) Documentation cleanup Drop extra empty lines and organize sysfs entries in a table. Signed-off-by: Kurt Borja Link: https://lore.kernel.org/r/20250908-sht2x-v4-1-bc15f68af7de@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht21.rst | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/Documentation/hwmon/sht21.rst b/Documentation/hwmon/sht21.rst index 1bccc8e8aac8..9f66cd51b45d 100644 --- a/Documentation/hwmon/sht21.rst +++ b/Documentation/hwmon/sht21.rst @@ -13,8 +13,6 @@ Supported chips: https://www.sensirion.com/file/datasheet_sht21 - - * Sensirion SHT25 Prefix: 'sht25' @@ -25,8 +23,6 @@ Supported chips: https://www.sensirion.com/file/datasheet_sht25 - - Author: Urs Fleisch @@ -47,13 +43,11 @@ in the board setup code. sysfs-Interface --------------- -temp1_input - - temperature input - -humidity1_input - - humidity input -eic - - Electronic Identification Code +=================== ============================================================ +temp1_input Temperature input +humidity1_input Humidity input +eic Electronic Identification Code +=================== ============================================================ Notes ----- -- cgit v1.2.3 From a0cce093689859238f923faa83a6735b7e8613c4 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Mon, 8 Sep 2025 10:54:50 -0500 Subject: hwmon: (sht21) Add support for SHT20, SHT25 chips All sht2x chips share the same communication protocol so add support for them. Signed-off-by: Kurt Borja Link: https://lore.kernel.org/r/20250908-sht2x-v4-2-bc15f68af7de@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/sht21.rst | 10 ++++++++++ drivers/hwmon/Kconfig | 4 ++-- drivers/hwmon/sht21.c | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/Documentation/hwmon/sht21.rst b/Documentation/hwmon/sht21.rst index 9f66cd51b45d..d20e8a460ba6 100644 --- a/Documentation/hwmon/sht21.rst +++ b/Documentation/hwmon/sht21.rst @@ -3,6 +3,16 @@ Kernel driver sht21 Supported chips: + * Sensirion SHT20 + + Prefix: 'sht20' + + Addresses scanned: none + + Datasheet: Publicly available at the Sensirion website + + https://www.sensirion.com/file/datasheet_sht20 + * Sensirion SHT21 Prefix: 'sht21' diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 8a41275ca518..bf291ba7ca32 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1930,8 +1930,8 @@ config SENSORS_SHT21 tristate "Sensiron humidity and temperature sensors. SHT21 and compat." depends on I2C help - If you say yes here you get support for the Sensiron SHT21, SHT25 - humidity and temperature sensors. + If you say yes here you get support for the Sensiron SHT20, SHT21, + SHT25 humidity and temperature sensors. This driver can also be built as a module. If so, the module will be called sht21. diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c index 97327313529b..97d71e3361e9 100644 --- a/drivers/hwmon/sht21.c +++ b/drivers/hwmon/sht21.c @@ -275,7 +275,9 @@ static int sht21_probe(struct i2c_client *client) /* Device ID table */ static const struct i2c_device_id sht21_id[] = { + { "sht20" }, { "sht21" }, + { "sht25" }, { } }; MODULE_DEVICE_TABLE(i2c, sht21_id); -- cgit v1.2.3 From 0ab88e2394392f475b8857ac82c0c987841217f8 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Mon, 8 Sep 2025 10:25:44 +0800 Subject: hwmon: add GPD devices sensor driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sensors driver for GPD Handhelds that expose fan reading and control via hwmon sysfs. Shenzhen GPD Technology Co., Ltd. manufactures a series of handheld devices. This driver implements these functions through x86 port-mapped IO. Tested-by: Marcin Strągowski Tested-by: someone5678 Tested-by: Justin Weiss Tested-by: Antheas Kapenekakis Tested-by: command_block Tested-by: derjohn Tested-by: Crashdummyy Signed-off-by: Cryolitia PukNgae Link: https://lore.kernel.org/r/20250908-gpd_fan-v9-1-7b4506c03953@uniontech.com Signed-off-by: Guenter Roeck --- MAINTAINERS | 6 + drivers/hwmon/Kconfig | 10 + drivers/hwmon/Makefile | 1 + drivers/hwmon/gpd-fan.c | 715 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 732 insertions(+) create mode 100644 drivers/hwmon/gpd-fan.c diff --git a/MAINTAINERS b/MAINTAINERS index 4e6f41ed4f2c..c21daede716d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10421,6 +10421,12 @@ F: drivers/phy/samsung/phy-gs101-ufs.c F: include/dt-bindings/clock/google,gs101.h K: [gG]oogle.?[tT]ensor +GPD FAN DRIVER +M: Cryolitia PukNgae +L: linux-hwmon@vger.kernel.org +S: Maintained +F: drivers/hwmon/gpd-fan.c + GPD POCKET FAN DRIVER M: Hans de Goede L: platform-driver-x86@vger.kernel.org diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index bf291ba7ca32..d6769288a76e 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -769,6 +769,16 @@ config SENSORS_GL520SM This driver can also be built as a module. If so, the module will be called gl520sm. +config SENSORS_GPD + tristate "GPD handhelds" + depends on X86 + help + If you say yes here you get support for fan readings and + control over GPD handheld devices. + + Can also be built as a module. In that case it will be + called gpd-fan. + config SENSORS_G760A tristate "GMT G760A" depends on I2C diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index cd8bc4752b4d..051981eb8a50 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -88,6 +88,7 @@ obj-$(CONFIG_SENSORS_GIGABYTE_WATERFORCE) += gigabyte_waterforce.o obj-$(CONFIG_SENSORS_GL518SM) += gl518sm.o obj-$(CONFIG_SENSORS_GL520SM) += gl520sm.o obj-$(CONFIG_SENSORS_GSC) += gsc-hwmon.o +obj-$(CONFIG_SENSORS_GPD) += gpd-fan.o obj-$(CONFIG_SENSORS_GPIO_FAN) += gpio-fan.o obj-$(CONFIG_SENSORS_GXP_FAN_CTRL) += gxp-fan-ctrl.o obj-$(CONFIG_SENSORS_HIH6130) += hih6130.o diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c new file mode 100644 index 000000000000..e0b3b46e1bf1 --- /dev/null +++ b/drivers/hwmon/gpd-fan.c @@ -0,0 +1,715 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* Platform driver for GPD devices that expose fan control via hwmon sysfs. + * + * Fan control is provided via pwm interface in the range [0-255]. + * Each model has a different range in the EC, the written value is scaled to + * accommodate for that. + * + * Based on this repo: + * https://github.com/Cryolitia/gpd-fan-driver + * + * Copyright (c) 2024 Cryolitia PukNgae + */ + +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "gpdfan" +#define GPD_PWM_CTR_OFFSET 0x1841 + +static char *gpd_fan_board = ""; +module_param(gpd_fan_board, charp, 0444); + +// EC read/write locker, protecting a sequence of EC operations +static DEFINE_MUTEX(gpd_fan_sequence_lock); + +enum gpd_board { + win_mini, + win4_6800u, + win_max_2, + duo, +}; + +enum FAN_PWM_ENABLE { + DISABLE = 0, + MANUAL = 1, + AUTOMATIC = 2, +}; + +static struct { + enum FAN_PWM_ENABLE pwm_enable; + u8 pwm_value; + + const struct gpd_fan_drvdata *drvdata; +} gpd_driver_priv; + +struct gpd_fan_drvdata { + const char *board_name; // Board name for module param comparison + const enum gpd_board board; + + const u8 addr_port; + const u8 data_port; + const u16 manual_control_enable; + const u16 rpm_read; + const u16 pwm_write; + const u16 pwm_max; +}; + +static struct gpd_fan_drvdata gpd_win_mini_drvdata = { + .board_name = "win_mini", + .board = win_mini, + + .addr_port = 0x4E, + .data_port = 0x4F, + .manual_control_enable = 0x047A, + .rpm_read = 0x0478, + .pwm_write = 0x047A, + .pwm_max = 244, +}; + +static struct gpd_fan_drvdata gpd_duo_drvdata = { + .board_name = "duo", + .board = duo, + + .addr_port = 0x4E, + .data_port = 0x4F, + .manual_control_enable = 0x047A, + .rpm_read = 0x0478, + .pwm_write = 0x047A, + .pwm_max = 244, +}; + +static struct gpd_fan_drvdata gpd_win4_drvdata = { + .board_name = "win4", + .board = win4_6800u, + + .addr_port = 0x2E, + .data_port = 0x2F, + .manual_control_enable = 0xC311, + .rpm_read = 0xC880, + .pwm_write = 0xC311, + .pwm_max = 127, +}; + +static struct gpd_fan_drvdata gpd_wm2_drvdata = { + .board_name = "wm2", + .board = win_max_2, + + .addr_port = 0x4E, + .data_port = 0x4F, + .manual_control_enable = 0x0275, + .rpm_read = 0x0218, + .pwm_write = 0x1809, + .pwm_max = 184, +}; + +static const struct dmi_system_id dmi_table[] = { + { + // GPD Win Mini + // GPD Win Mini with AMD Ryzen 8840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1617-01") + }, + .driver_data = &gpd_win_mini_drvdata, + }, + { + // GPD Win Mini + // GPD Win Mini with AMD Ryzen HX370 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02") + }, + .driver_data = &gpd_win_mini_drvdata, + }, + { + // GPD Win Mini + // GPD Win Mini with AMD Ryzen HX370 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1617-02-L") + }, + .driver_data = &gpd_win_mini_drvdata, + }, + { + // GPD Win 4 with AMD Ryzen 6800U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), + DMI_MATCH(DMI_BOARD_VERSION, "Default string"), + }, + .driver_data = &gpd_win4_drvdata, + }, + { + // GPD Win 4 with Ryzen 7840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), + DMI_MATCH(DMI_BOARD_VERSION, "Ver. 1.0"), + }, + // Since 7840U, win4 uses the same drvdata as wm2 + .driver_data = &gpd_wm2_drvdata, + }, + { + // GPD Win 4 with Ryzen 7840U (another) + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1618-04"), + DMI_MATCH(DMI_BOARD_VERSION, "Ver.1.0"), + }, + .driver_data = &gpd_wm2_drvdata, + }, + { + // GPD Win Max 2 with Ryzen 6800U + // GPD Win Max 2 2023 with Ryzen 7840U + // GPD Win Max 2 2024 with Ryzen 8840U + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1619-04"), + }, + .driver_data = &gpd_wm2_drvdata, + }, + { + // GPD Win Max 2 with AMD Ryzen HX370 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1619-05"), + }, + .driver_data = &gpd_wm2_drvdata, + }, + { + // GPD Duo + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01"), + }, + .driver_data = &gpd_duo_drvdata, + }, + { + // GPD Duo (another) + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1622-01-L"), + }, + .driver_data = &gpd_duo_drvdata, + }, + { + // GPD Pocket 4 + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04"), + }, + .driver_data = &gpd_win_mini_drvdata, + }, + { + // GPD Pocket 4 (another) + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "GPD"), + DMI_MATCH(DMI_PRODUCT_NAME, "G1628-04-L"), + }, + .driver_data = &gpd_win_mini_drvdata, + }, + {} +}; + +static const struct gpd_fan_drvdata *gpd_module_drvdata[] = { + &gpd_win_mini_drvdata, &gpd_win4_drvdata, &gpd_wm2_drvdata, NULL +}; + +// Helper functions to handle EC read/write +static void gpd_ecram_read(u16 offset, u8 *val) +{ + u16 addr_port = gpd_driver_priv.drvdata->addr_port; + u16 data_port = gpd_driver_priv.drvdata->data_port; + + outb(0x2E, addr_port); + outb(0x11, data_port); + outb(0x2F, addr_port); + outb((u8)((offset >> 8) & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x10, data_port); + outb(0x2F, addr_port); + outb((u8)(offset & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x12, data_port); + outb(0x2F, addr_port); + *val = inb(data_port); +} + +static void gpd_ecram_write(u16 offset, u8 value) +{ + u16 addr_port = gpd_driver_priv.drvdata->addr_port; + u16 data_port = gpd_driver_priv.drvdata->data_port; + + outb(0x2E, addr_port); + outb(0x11, data_port); + outb(0x2F, addr_port); + outb((u8)((offset >> 8) & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x10, data_port); + outb(0x2F, addr_port); + outb((u8)(offset & 0xFF), data_port); + + outb(0x2E, addr_port); + outb(0x12, data_port); + outb(0x2F, addr_port); + outb(value, data_port); +} + +static int gpd_generic_read_rpm(void) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 high, low; + + gpd_ecram_read(drvdata->rpm_read, &high); + gpd_ecram_read(drvdata->rpm_read + 1, &low); + + return (u16)high << 8 | low; +} + +static void gpd_win4_init_ec(void) +{ + u8 chip_id, chip_ver; + + gpd_ecram_read(0x2000, &chip_id); + + if (chip_id == 0x55) { + gpd_ecram_read(0x1060, &chip_ver); + gpd_ecram_write(0x1060, chip_ver | 0x80); + } +} + +static int gpd_win4_read_rpm(void) +{ + int ret; + + ret = gpd_generic_read_rpm(); + + if (ret == 0) + // Re-init EC when speed is 0 + gpd_win4_init_ec(); + + return ret; +} + +static int gpd_wm2_read_rpm(void) +{ + for (u16 pwm_ctr_offset = GPD_PWM_CTR_OFFSET; + pwm_ctr_offset <= GPD_PWM_CTR_OFFSET + 2; pwm_ctr_offset++) { + u8 PWMCTR; + + gpd_ecram_read(pwm_ctr_offset, &PWMCTR); + + if (PWMCTR != 0xB8) + gpd_ecram_write(pwm_ctr_offset, 0xB8); + } + + return gpd_generic_read_rpm(); +} + +// Read value for fan1_input +static int gpd_read_rpm(void) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + case duo: + return gpd_generic_read_rpm(); + case win4_6800u: + return gpd_win4_read_rpm(); + case win_max_2: + return gpd_wm2_read_rpm(); + } + + return 0; +} + +static int gpd_wm2_read_pwm(void) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 var; + + gpd_ecram_read(drvdata->pwm_write, &var); + + // Match gpd_generic_write_pwm(u8) below + return DIV_ROUND_CLOSEST((var - 1) * 255, (drvdata->pwm_max - 1)); +} + +// Read value for pwm1 +static int gpd_read_pwm(void) +{ + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + case duo: + case win4_6800u: + switch (gpd_driver_priv.pwm_enable) { + case DISABLE: + return 255; + case MANUAL: + return gpd_driver_priv.pwm_value; + case AUTOMATIC: + return -EOPNOTSUPP; + } + break; + case win_max_2: + return gpd_wm2_read_pwm(); + } + return 0; +} + +// PWM value's range in EC is 1 - pwm_max, cast 0 - 255 to it. +static inline u8 gpd_cast_pwm_range(u8 val) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + + return DIV_ROUND_CLOSEST(val * (drvdata->pwm_max - 1), 255) + 1; +} + +static void gpd_generic_write_pwm(u8 val) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 pwm_reg; + + pwm_reg = gpd_cast_pwm_range(val); + gpd_ecram_write(drvdata->pwm_write, pwm_reg); +} + +static void gpd_duo_write_pwm(u8 val) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + u8 pwm_reg; + + pwm_reg = gpd_cast_pwm_range(val); + gpd_ecram_write(drvdata->pwm_write, pwm_reg); + gpd_ecram_write(drvdata->pwm_write + 1, pwm_reg); +} + +// Write value for pwm1 +static int gpd_write_pwm(u8 val) +{ + if (gpd_driver_priv.pwm_enable != MANUAL) + return -EPERM; + + switch (gpd_driver_priv.drvdata->board) { + case duo: + gpd_duo_write_pwm(val); + break; + case win_mini: + case win4_6800u: + case win_max_2: + gpd_generic_write_pwm(val); + break; + } + + return 0; +} + +static void gpd_win_mini_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) +{ + switch (pwm_enable) { + case DISABLE: + gpd_generic_write_pwm(255); + break; + case MANUAL: + gpd_generic_write_pwm(gpd_driver_priv.pwm_value); + break; + case AUTOMATIC: + gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0); + break; + } +} + +static void gpd_duo_set_pwm_enable(enum FAN_PWM_ENABLE pwm_enable) +{ + switch (pwm_enable) { + case DISABLE: + gpd_duo_write_pwm(255); + break; + case MANUAL: + gpd_duo_write_pwm(gpd_driver_priv.pwm_value); + break; + case AUTOMATIC: + gpd_ecram_write(gpd_driver_priv.drvdata->pwm_write, 0); + break; + } +} + +static void gpd_wm2_set_pwm_enable(enum FAN_PWM_ENABLE enable) +{ + const struct gpd_fan_drvdata *const drvdata = gpd_driver_priv.drvdata; + + switch (enable) { + case DISABLE: + gpd_generic_write_pwm(255); + gpd_ecram_write(drvdata->manual_control_enable, 1); + break; + case MANUAL: + gpd_generic_write_pwm(gpd_driver_priv.pwm_value); + gpd_ecram_write(drvdata->manual_control_enable, 1); + break; + case AUTOMATIC: + gpd_ecram_write(drvdata->manual_control_enable, 0); + break; + } +} + +// Write value for pwm1_enable +static void gpd_set_pwm_enable(enum FAN_PWM_ENABLE enable) +{ + if (enable == MANUAL) + // Set pwm_value to max firstly when switching to manual mode, in + // consideration of device safety. + gpd_driver_priv.pwm_value = 255; + + switch (gpd_driver_priv.drvdata->board) { + case win_mini: + case win4_6800u: + gpd_win_mini_set_pwm_enable(enable); + break; + case duo: + gpd_duo_set_pwm_enable(enable); + break; + case win_max_2: + gpd_wm2_set_pwm_enable(enable); + break; + } +} + +static umode_t gpd_fan_hwmon_is_visible(__always_unused const void *drvdata, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel) +{ + if (type == hwmon_fan && attr == hwmon_fan_input) { + return 0444; + } else if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + case hwmon_pwm_input: + return 0644; + default: + return 0; + } + } + return 0; +} + +static int gpd_fan_hwmon_read(__always_unused struct device *dev, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel, long *val) +{ + int ret; + + ret = mutex_lock_interruptible(&gpd_fan_sequence_lock); + if (ret) + return ret; + + if (type == hwmon_fan) { + if (attr == hwmon_fan_input) { + ret = gpd_read_rpm(); + + if (ret < 0) + goto OUT; + + *val = ret; + ret = 0; + goto OUT; + } + } else if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + *val = gpd_driver_priv.pwm_enable; + ret = 0; + goto OUT; + case hwmon_pwm_input: + ret = gpd_read_pwm(); + + if (ret < 0) + goto OUT; + + *val = ret; + ret = 0; + goto OUT; + } + } + + ret = -EOPNOTSUPP; + +OUT: + mutex_unlock(&gpd_fan_sequence_lock); + return ret; +} + +static int gpd_fan_hwmon_write(__always_unused struct device *dev, + enum hwmon_sensor_types type, u32 attr, + __always_unused int channel, long val) +{ + int ret; + + ret = mutex_lock_interruptible(&gpd_fan_sequence_lock); + if (ret) + return ret; + + if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_enable: + if (!in_range(val, 0, 3)) { + ret = -EINVAL; + goto OUT; + } + + gpd_driver_priv.pwm_enable = val; + + gpd_set_pwm_enable(gpd_driver_priv.pwm_enable); + ret = 0; + goto OUT; + case hwmon_pwm_input: + if (!in_range(val, 0, 255)) { + ret = -ERANGE; + goto OUT; + } + + gpd_driver_priv.pwm_value = val; + + ret = gpd_write_pwm(val); + goto OUT; + } + } + + ret = -EOPNOTSUPP; + +OUT: + mutex_unlock(&gpd_fan_sequence_lock); + return ret; +} + +static const struct hwmon_ops gpd_fan_ops = { + .is_visible = gpd_fan_hwmon_is_visible, + .read = gpd_fan_hwmon_read, + .write = gpd_fan_hwmon_write, +}; + +static const struct hwmon_channel_info *gpd_fan_hwmon_channel_info[] = { + HWMON_CHANNEL_INFO(fan, HWMON_F_INPUT), + HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE), + NULL +}; + +static struct hwmon_chip_info gpd_fan_chip_info = { + .ops = &gpd_fan_ops, + .info = gpd_fan_hwmon_channel_info +}; + +static int gpd_fan_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct resource *region; + const struct resource *res; + const struct device *hwdev; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (IS_ERR(res)) + return dev_err_probe(dev, PTR_ERR(res), + "Failed to get platform resource\n"); + + region = devm_request_region(dev, res->start, + resource_size(res), DRIVER_NAME); + if (IS_ERR(region)) + return dev_err_probe(dev, PTR_ERR(region), + "Failed to request region\n"); + + hwdev = devm_hwmon_device_register_with_info(dev, + DRIVER_NAME, + NULL, + &gpd_fan_chip_info, + NULL); + if (IS_ERR(hwdev)) + return dev_err_probe(dev, PTR_ERR(region), + "Failed to register hwmon device\n"); + + return 0; +} + +static void gpd_fan_remove(__always_unused struct platform_device *pdev) +{ + gpd_driver_priv.pwm_enable = AUTOMATIC; + gpd_set_pwm_enable(AUTOMATIC); +} + +static struct platform_driver gpd_fan_driver = { + .probe = gpd_fan_probe, + .remove = gpd_fan_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static struct platform_device *gpd_fan_platform_device; + +static int __init gpd_fan_init(void) +{ + const struct gpd_fan_drvdata *match = NULL; + + for (const struct gpd_fan_drvdata **p = gpd_module_drvdata; *p; p++) { + if (strcmp(gpd_fan_board, (*p)->board_name) == 0) { + match = *p; + break; + } + } + + if (!match) { + const struct dmi_system_id *dmi_match = + dmi_first_match(dmi_table); + if (dmi_match) + match = dmi_match->driver_data; + } + + if (!match) + return -ENODEV; + + gpd_driver_priv.pwm_enable = AUTOMATIC; + gpd_driver_priv.pwm_value = 255; + gpd_driver_priv.drvdata = match; + + struct resource gpd_fan_resources[] = { + { + .start = match->addr_port, + .end = match->data_port, + .flags = IORESOURCE_IO, + }, + }; + + gpd_fan_platform_device = platform_create_bundle(&gpd_fan_driver, + gpd_fan_probe, + gpd_fan_resources, + 1, NULL, 0); + + if (IS_ERR(gpd_fan_platform_device)) { + pr_warn("Failed to create platform device\n"); + return PTR_ERR(gpd_fan_platform_device); + } + + return 0; +} + +static void __exit gpd_fan_exit(void) +{ + platform_device_unregister(gpd_fan_platform_device); + platform_driver_unregister(&gpd_fan_driver); +} + +MODULE_DEVICE_TABLE(dmi, dmi_table); + +module_init(gpd_fan_init); +module_exit(gpd_fan_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Cryolitia PukNgae "); +MODULE_DESCRIPTION("GPD Devices fan control driver"); -- cgit v1.2.3 From 69001f21ded78131b995af2900ef574b04a59ae2 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Mon, 8 Sep 2025 10:25:45 +0800 Subject: hwmon: document: add gpd-fan Add GPD fan driver document Signed-off-by: Cryolitia PukNgae Link: https://lore.kernel.org/r/20250908-gpd_fan-v9-2-7b4506c03953@uniontech.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/gpd-fan.rst | 78 +++++++++++++++++++++++++++++++++++++++++ Documentation/hwmon/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 80 insertions(+) create mode 100644 Documentation/hwmon/gpd-fan.rst diff --git a/Documentation/hwmon/gpd-fan.rst b/Documentation/hwmon/gpd-fan.rst new file mode 100644 index 000000000000..0b56b70e6264 --- /dev/null +++ b/Documentation/hwmon/gpd-fan.rst @@ -0,0 +1,78 @@ +.. SPDX-License-Identifier: GPL-2.0-or-later + +Kernel driver gpd-fan +========================= + +Author: + - Cryolitia PukNgae + +Description +------------ + +Handheld devices from Shenzhen GPD Technology Co., Ltd. provide fan readings +and fan control through their embedded controllers. + +Supported devices +----------------- + +Currently the driver supports the following handhelds: + + - GPD Win Mini (7840U) + - GPD Win Mini (8840U) + - GPD Win Mini (HX370) + - GPD Pocket 4 + - GPD Duo + - GPD Win Max 2 (6800U) + - GPD Win Max 2 2023 (7840U) + - GPD Win Max 2 2024 (8840U) + - GPD Win Max 2 2025 (HX370) + - GPD Win 4 (6800U) + - GPD Win 4 (7840U) + +Module parameters +----------------- + +gpd_fan_board + Force specific which module quirk should be used. + Use it like "gpd_fan_board=wm2". + + - wm2 + - GPD Win 4 (7840U) + - GPD Win Max 2 (6800U) + - GPD Win Max 2 2023 (7840U) + - GPD Win Max 2 2024 (8840U) + - GPD Win Max 2 2025 (HX370) + - win4 + - GPD Win 4 (6800U) + - win_mini + - GPD Win Mini (7840U) + - GPD Win Mini (8840U) + - GPD Win Mini (HX370) + - GPD Pocket 4 + - GPD Duo + +Sysfs entries +------------- + +The following attributes are supported: + +fan1_input + Read Only. Reads current fan RPM. + +pwm1_enable + Read/Write. Enable manual fan control. Write "0" to disable control and run + at full speed. Write "1" to set to manual, write "2" to let the EC control + decide fan speed. Read this attribute to see current status. + + NB:In consideration of the safety of the device, when setting to manual mode, + the pwm speed will be set to the maximum value (255) by default. You can set + a different value by writing pwm1 later. + +pwm1 + Read/Write. Read this attribute to see current duty cycle in the range + [0-255]. When pwm1_enable is set to "1" (manual) write any value in the + range [0-255] to set fan speed. + + NB: Many boards (except listed under wm2 above) don't support reading the + current pwm value in auto mode. That will just return EOPNOTSUPP. In manual + mode it will always return the real value. diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index b4d26a6fa3a5..1803a43f6216 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -82,6 +82,7 @@ Hardware Monitoring Kernel Drivers gigabyte_waterforce gsc-hwmon gl518sm + gpd-fan gxp-fan-ctrl hih6130 hp-wmi-sensors diff --git a/MAINTAINERS b/MAINTAINERS index c21daede716d..5642fd647326 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10425,6 +10425,7 @@ GPD FAN DRIVER M: Cryolitia PukNgae L: linux-hwmon@vger.kernel.org S: Maintained +F: Documentation/hwmon/gpd-fan.rst F: drivers/hwmon/gpd-fan.c GPD POCKET FAN DRIVER -- cgit v1.2.3 From 7e5969a4d3e794993c9ca8d4026cf31a34b32b30 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Mon, 8 Sep 2025 10:54:51 -0500 Subject: dt-bindings: trivial-devices: Add sht2x sensors Add sensirion,sht2x trivial sensors. Signed-off-by: Kurt Borja Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250908-sht2x-v4-3-bc15f68af7de@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index 0e6ba6e12a63..edaba5cd8434 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -372,6 +372,9 @@ properties: # Sensirion low power multi-pixel gas sensor with I2C interface - sensirion,sgpc3 # Sensirion temperature & humidity sensor with I2C interface + - sensirion,sht20 + - sensirion,sht21 + - sensirion,sht25 - sensirion,sht4x # Sensortek 3 axis accelerometer - sensortek,stk8312 -- cgit v1.2.3 From 393de14673d60384f8f014b75fe679a69c9110e9 Mon Sep 17 00:00:00 2001 From: Kurt Borja Date: Mon, 8 Sep 2025 10:54:52 -0500 Subject: hwmon: (sht21) Add devicetree support Add DT support for sht2x chips. Signed-off-by: Kurt Borja Link: https://lore.kernel.org/r/20250908-sht2x-v4-4-bc15f68af7de@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/sht21.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/sht21.c b/drivers/hwmon/sht21.c index 97d71e3361e9..627d35070a42 100644 --- a/drivers/hwmon/sht21.c +++ b/drivers/hwmon/sht21.c @@ -282,8 +282,19 @@ static const struct i2c_device_id sht21_id[] = { }; MODULE_DEVICE_TABLE(i2c, sht21_id); +static const struct of_device_id sht21_of_match[] = { + { .compatible = "sensirion,sht20" }, + { .compatible = "sensirion,sht21" }, + { .compatible = "sensirion,sht25" }, + { } +}; +MODULE_DEVICE_TABLE(of, sht21_of_match); + static struct i2c_driver sht21_driver = { - .driver.name = "sht21", + .driver = { + .name = "sht21", + .of_match_table = sht21_of_match, + }, .probe = sht21_probe, .id_table = sht21_id, }; -- cgit v1.2.3 From 80038a758b7fc0cdb6987532cbbf3f75b13e0826 Mon Sep 17 00:00:00 2001 From: Andreas Kemnade Date: Tue, 9 Sep 2025 10:02:49 +0200 Subject: hwmon: sy7636a: add alias Add module alias to have it autoloaded. Signed-off-by: Andreas Kemnade Link: https://lore.kernel.org/r/20250909080249.30656-1-andreas@kemnade.info Signed-off-by: Guenter Roeck --- drivers/hwmon/sy7636a-hwmon.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/hwmon/sy7636a-hwmon.c b/drivers/hwmon/sy7636a-hwmon.c index ed110884786b..a12fc0ce70e7 100644 --- a/drivers/hwmon/sy7636a-hwmon.c +++ b/drivers/hwmon/sy7636a-hwmon.c @@ -104,3 +104,4 @@ module_platform_driver(sy7636a_sensor_driver); MODULE_DESCRIPTION("SY7636A sensor driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:sy7636a-temperature"); -- cgit v1.2.3 From bca9b6633fb92ff37247442d08f3889a2e87881c Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Tue, 16 Sep 2025 17:50:25 +0800 Subject: dt-bindings: trivial-devices: add mps,mp5998 Add dt-bindings for MPS mp5998 hot-swap controller. Signed-off-by: Cosmo Chou Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250916095026.800164-1-chou.cosmo@gmail.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/trivial-devices.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/trivial-devices.yaml b/Documentation/devicetree/bindings/trivial-devices.yaml index edaba5cd8434..12cb7f64b988 100644 --- a/Documentation/devicetree/bindings/trivial-devices.yaml +++ b/Documentation/devicetree/bindings/trivial-devices.yaml @@ -315,6 +315,8 @@ properties: - mps,mp5920 # Monolithic Power Systems Inc. multi-phase hot-swap controller mp5990 - mps,mp5990 + # Monolithic Power Systems Inc. multi-phase hot-swap controller mp5998 + - mps,mp5998 # Monolithic Power Systems Inc. digital step-down converter mp9941 - mps,mp9941 # Temperature sensor with integrated fan control -- cgit v1.2.3 From bef3c793542b132ab1dc19076ce4f91594b5fbdc Mon Sep 17 00:00:00 2001 From: Cosmo Chou Date: Tue, 16 Sep 2025 17:50:26 +0800 Subject: hwmon: (pmbus/mp5990) add support for MP5998 Add support for the MPS MP5998 hot-swap controller. Like MP5990, MP5998 uses EFUSE_CFG (0xC4) bit 9 to indicate linear/direct data format. Signed-off-by: Cosmo Chou Link: https://lore.kernel.org/r/20250916095026.800164-2-chou.cosmo@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/mp5990.rst | 30 ++++++++++++++++--- drivers/hwmon/pmbus/mp5990.c | 67 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 85 insertions(+), 12 deletions(-) diff --git a/Documentation/hwmon/mp5990.rst b/Documentation/hwmon/mp5990.rst index 6f2f0c099d44..7fd536757ff2 100644 --- a/Documentation/hwmon/mp5990.rst +++ b/Documentation/hwmon/mp5990.rst @@ -9,9 +9,13 @@ Supported chips: Prefix: 'mp5990' - * Datasheet + Datasheet: Publicly available at the MPS website: https://www.monolithicpower.com/en/mp5990.html - Publicly available at the MPS website : https://www.monolithicpower.com/en/mp5990.html + * MPS MP5998 + + Prefix: 'mp5998' + + Datasheet: Not publicly available Author: @@ -21,7 +25,7 @@ Description ----------- This driver implements support for Monolithic Power Systems, Inc. (MPS) -MP5990 Hot-Swap Controller. +MP5990 and MP5998 Hot-Swap Controller. Device compliant with: @@ -53,7 +57,7 @@ The driver provides the following attributes for output voltage: **in2_alarm** -The driver provides the following attributes for output current: +The driver provides the following attributes for current: **curr1_input** @@ -63,6 +67,14 @@ The driver provides the following attributes for output current: **curr1_max** +**curr2_input** + +**curr2_label** + +**curr2_max** + +**curr2_max_alarm** + The driver provides the following attributes for input power: **power1_input** @@ -71,6 +83,16 @@ The driver provides the following attributes for input power: **power1_alarm** +The driver provides the following attributes for output power: + +**power2_input** + +**power2_label** + +**power2_max** + +**power2_max_alarm** + The driver provides the following attributes for temperature: **temp1_input** diff --git a/drivers/hwmon/pmbus/mp5990.c b/drivers/hwmon/pmbus/mp5990.c index 4ce381a39480..9a4ee79712cf 100644 --- a/drivers/hwmon/pmbus/mp5990.c +++ b/drivers/hwmon/pmbus/mp5990.c @@ -8,6 +8,8 @@ #include #include "pmbus.h" +enum chips { mp5990, mp5998 }; + #define MP5990_EFUSE_CFG (0xC4) #define MP5990_VOUT_FORMAT BIT(9) @@ -110,10 +112,53 @@ static struct pmbus_driver_info mp5990_info = { .read_word_data = mp5990_read_word_data, }; +static struct pmbus_driver_info mp5998_info = { + .pages = 1, + .format[PSC_VOLTAGE_IN] = direct, + .format[PSC_VOLTAGE_OUT] = direct, + .format[PSC_CURRENT_IN] = direct, + .format[PSC_CURRENT_OUT] = direct, + .format[PSC_POWER] = direct, + .format[PSC_TEMPERATURE] = direct, + .m[PSC_VOLTAGE_IN] = 64, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 0, + .m[PSC_VOLTAGE_OUT] = 64, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 0, + .m[PSC_CURRENT_IN] = 16, + .b[PSC_CURRENT_IN] = 0, + .R[PSC_CURRENT_IN] = 0, + .m[PSC_CURRENT_OUT] = 16, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 0, + .m[PSC_POWER] = 2, + .b[PSC_POWER] = 0, + .R[PSC_POWER] = 0, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 0, + .func[0] = + PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | + PMBUS_HAVE_IIN | PMBUS_HAVE_PIN | PMBUS_HAVE_POUT | + PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_IOUT | + PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP, + .read_byte_data = mp5990_read_byte_data, + .read_word_data = mp5990_read_word_data, +}; + +static const struct i2c_device_id mp5990_id[] = { + {"mp5990", mp5990}, + {"mp5998", mp5998}, + { } +}; +MODULE_DEVICE_TABLE(i2c, mp5990_id); + static int mp5990_probe(struct i2c_client *client) { struct pmbus_driver_info *info; struct mp5990_data *data; + enum chips chip; int ret; data = devm_kzalloc(&client->dev, sizeof(struct mp5990_data), @@ -121,7 +166,15 @@ static int mp5990_probe(struct i2c_client *client) if (!data) return -ENOMEM; - memcpy(&data->info, &mp5990_info, sizeof(*info)); + if (client->dev.of_node) + chip = (uintptr_t)of_device_get_match_data(&client->dev); + else + chip = i2c_match_id(mp5990_id, client)->driver_data; + + if (chip == mp5990) + memcpy(&data->info, &mp5990_info, sizeof(*info)); + else + memcpy(&data->info, &mp5998_info, sizeof(*info)); info = &data->info; /* Read Vout Config */ @@ -140,6 +193,9 @@ static int mp5990_probe(struct i2c_client *client) data->info.format[PSC_VOLTAGE_OUT] = linear; data->info.format[PSC_CURRENT_OUT] = linear; data->info.format[PSC_POWER] = linear; + if (chip == mp5998) + data->info.format[PSC_CURRENT_IN] = linear; + ret = i2c_smbus_read_word_data(client, PMBUS_READ_VOUT); if (ret < 0) { dev_err(&client->dev, "Can't get vout exponent."); @@ -153,16 +209,11 @@ static int mp5990_probe(struct i2c_client *client) } static const struct of_device_id mp5990_of_match[] = { - { .compatible = "mps,mp5990" }, + { .compatible = "mps,mp5990", .data = (void *)mp5990 }, + { .compatible = "mps,mp5998", .data = (void *)mp5998 }, {} }; -static const struct i2c_device_id mp5990_id[] = { - {"mp5990"}, - { } -}; -MODULE_DEVICE_TABLE(i2c, mp5990_id); - static struct i2c_driver mp5990_driver = { .driver = { .name = "mp5990", -- cgit v1.2.3 From 1fac317b6cae373d5d1a5695a5175c65c745922f Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Fri, 19 Sep 2025 16:38:49 +0800 Subject: hwmon: (gpd-fan) Fix range check for pwm input Fixed the maximum value in the PWM input range check, allowing the input to be set to 255. Fixes: 0ab88e239439 ("hwmon: add GPD devices sensor driver") Reported-by: Chenx Dust Link: https://github.com/Cryolitia/gpd-fan-driver/pull/18 Co-developed-by: Chenx Dust Signed-off-by: Chenx Dust Signed-off-by: Cryolitia PukNgae Link: https://lore.kernel.org/r/20250919-hwmon-v1-1-2b69c8b9c062@uniontech.com Signed-off-by: Guenter Roeck --- drivers/hwmon/gpd-fan.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/gpd-fan.c b/drivers/hwmon/gpd-fan.c index e0b3b46e1bf1..644dc3ca9df7 100644 --- a/drivers/hwmon/gpd-fan.c +++ b/drivers/hwmon/gpd-fan.c @@ -571,7 +571,7 @@ static int gpd_fan_hwmon_write(__always_unused struct device *dev, ret = 0; goto OUT; case hwmon_pwm_input: - if (!in_range(val, 0, 255)) { + if (!in_range(val, 0, 256)) { ret = -ERANGE; goto OUT; } -- cgit v1.2.3 From 5d5ec7c81c372d33875e0176f9f6d68e3e813e32 Mon Sep 17 00:00:00 2001 From: Shane Fagan Date: Sun, 14 Sep 2025 09:41:14 +0200 Subject: hwmon: (asus-ec-sensors) add ROG STRIX X670E-E GAMING WIFI Add support for ROG STRIX X670E-E GAMING WIFI Signed-off-by: Shane Fagan Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250914074125.135656-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 6a9c5f30e016..836d41373848 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -33,6 +33,7 @@ Supported boards: * ROG STRIX X570-E GAMING WIFI II * ROG STRIX X570-F GAMING * ROG STRIX X570-I GAMING + * ROG STRIX X670E-E GAMING WIFI * ROG STRIX X670E-I GAMING WIFI * ROG STRIX X870-I GAMING WIFI * ROG STRIX Z390-F GAMING diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 32d4dd26aa84..3f6d89bcc8a2 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -614,6 +614,13 @@ static const struct ec_board_info board_info_strix_x570_i_gaming = { .family = family_amd_500_series, }; +static const struct ec_board_info board_info_strix_x670e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_600_series, +}; + static const struct ec_board_info board_info_strix_x670e_i_gaming_wifi = { .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | SENSOR_TEMP_MB | SENSOR_TEMP_VRM, @@ -764,6 +771,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x570_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X570-I GAMING", &board_info_strix_x570_i_gaming), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-E GAMING WIFI", + &board_info_strix_x670e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X670E-I GAMING WIFI", &board_info_strix_x670e_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI", -- cgit v1.2.3 From ddb61e737f04e3c6c8299c1e00bf17a42a7f05cf Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 17 Sep 2025 20:10:33 +0200 Subject: hwmon: (dell-smm) Remove Dell Precision 490 custom config data It turns out the second fan on the Dell Precision 490 does not really support I8K_FAN_TURBO. Setting the fan state to 3 enables automatic fan control, just like on the other two fans. The reason why this was misinterpreted as turbo mode was that the second fan normally spins faster in automatic mode than in the previous fan states. Yet when in state 3, the fan speed reacts to heat exposure, exposing the automatic mode setting. Link: https://github.com/lm-sensors/lm-sensors/pull/383 Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250917181036.10972-2-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 1e2c8e284001..3f61b2d7935e 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1331,7 +1331,6 @@ struct i8k_config_data { enum i8k_configs { DELL_LATITUDE_D520, - DELL_PRECISION_490, DELL_STUDIO, DELL_XPS, }; @@ -1341,10 +1340,6 @@ static const struct i8k_config_data i8k_config_data[] __initconst = { .fan_mult = 1, .fan_max = I8K_FAN_TURBO, }, - [DELL_PRECISION_490] = { - .fan_mult = 1, - .fan_max = I8K_FAN_TURBO, - }, [DELL_STUDIO] = { .fan_mult = 1, .fan_max = I8K_FAN_HIGH, @@ -1364,15 +1359,6 @@ static const struct dmi_system_id i8k_config_dmi_table[] __initconst = { }, .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], }, - { - .ident = "Dell Precision 490", - .matches = { - DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), - DMI_MATCH(DMI_PRODUCT_NAME, - "Precision WorkStation 490"), - }, - .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], - }, { .ident = "Dell Studio", .matches = { -- cgit v1.2.3 From b3499883c6d5f968f44e87d021ff2bd47ab5d094 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 17 Sep 2025 20:10:34 +0200 Subject: hwmon: (dell-smm) Move clamping of fan speed out of i8k_set_fan() Currently i8k_set_fan() clamps the fan speed before performing the SMM call to ensure that the speed is not negative and not greater than i8k_fan_max. This however is mostly unnecessary as the hwmon and thermal interfaces alread ensure this. Only the legacy ioctl interface does not ensure that the fan speed passed to i8k_set_fan() does meet the above criteria. Move the clamping out of i8k_set_fan() and into the legacy ioctl handler to prepare for future changes. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250917181036.10972-3-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 3f61b2d7935e..36576db09706 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -446,7 +447,6 @@ static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed) if (disallow_fan_support) return -EINVAL; - speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); regs.ebx = fan | (speed << 8); return dell_smm_call(data->ops, ®s); @@ -637,6 +637,8 @@ static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (copy_from_user(&speed, argp + 1, sizeof(int))) return -EFAULT; + speed = clamp_val(speed, 0, data->i8k_fan_max); + mutex_lock(&data->i8k_mutex); err = i8k_set_fan(data, val, speed); if (err < 0) -- cgit v1.2.3 From 2c8ac03aad7a8dd649de9080503c68319afb43f9 Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Tue, 23 Sep 2025 21:26:55 +0200 Subject: hwmon: (asus-ec-sensors) add ROG STRIX X870E-E GAMING WIFI Add support for ROG STRIX X870E-E GAMING WIFI This board uses the same sensor configuration as the ProArt X870E-CREATOR WIFI motherboard. Signed-off-by: Ben Copeland Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250923192935.11339-2-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 836d41373848..5801bc4279c0 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -36,6 +36,7 @@ Supported boards: * ROG STRIX X670E-E GAMING WIFI * ROG STRIX X670E-I GAMING WIFI * ROG STRIX X870-I GAMING WIFI + * ROG STRIX X870E-E GAMING WIFI * ROG STRIX Z390-F GAMING * ROG STRIX Z490-F GAMING * ROG STRIX Z690-A GAMING WIFI D4 diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 3f6d89bcc8a2..7bb554c48143 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -635,6 +635,14 @@ static const struct ec_board_info board_info_strix_x870_i_gaming_wifi = { .family = family_amd_800_series, }; +static const struct ec_board_info board_info_strix_x870e_e_gaming_wifi = { + .sensors = SENSOR_TEMP_CPU | SENSOR_TEMP_CPU_PACKAGE | + SENSOR_TEMP_MB | SENSOR_TEMP_VRM | + SENSOR_FAN_CPU_OPT, + .mutex_path = ASUS_HW_ACCESS_MUTEX_SB_PCI0_SBRG_SIO1_MUT0, + .family = family_amd_800_series, +}; + static const struct ec_board_info board_info_strix_z390_f_gaming = { .sensors = SENSOR_TEMP_CHIPSET | SENSOR_TEMP_VRM | SENSOR_TEMP_T_SENSOR | @@ -777,6 +785,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_strix_x670e_i_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870-I GAMING WIFI", &board_info_strix_x870_i_gaming_wifi), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX X870E-E GAMING WIFI", + &board_info_strix_x870e_e_gaming_wifi), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z390-F GAMING", &board_info_strix_z390_f_gaming), DMI_EXACT_MATCH_ASUS_BOARD_NAME("ROG STRIX Z490-F GAMING", -- cgit v1.2.3 From 584d55be66ef151e6ef9ccb3dcbc0a2155559be1 Mon Sep 17 00:00:00 2001 From: Ben Copeland Date: Tue, 23 Sep 2025 21:26:56 +0200 Subject: hwmon: (asus-ec-sensors) increase timeout for locking ACPI mutex Some motherboards require more time to acquire the ACPI mutex, causing "Failed to acquire mutex" messages to appear in the kernel log. Increase the timeout from 500ms to 800ms to accommodate these cases. Signed-off-by: Ben Copeland Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250923192935.11339-3-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- drivers/hwmon/asus-ec-sensors.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index 7bb554c48143..e27a7b08e7b7 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -49,7 +49,7 @@ static char *mutex_path_override; */ #define ASUS_EC_MAX_BANK 3 -#define ACPI_LOCK_DELAY_MS 500 +#define ACPI_LOCK_DELAY_MS 800 /* ACPI mutex for locking access to the EC for the firmware */ #define ASUS_HW_ACCESS_MUTEX_ASMX "\\AMW0.ASMX" -- cgit v1.2.3 From 205c730262215fe1940668394a856f69ece55c66 Mon Sep 17 00:00:00 2001 From: Cryolitia PukNgae Date: Wed, 24 Sep 2025 15:48:38 +0800 Subject: hwmon: (gpd-fan) complete Kconfig dependencies DMI and HAS_IOPORT is also needed Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202509200214.i2QX7iwD-lkp@intel.com/ Signed-off-by: Cryolitia PukNgae Link: https://lore.kernel.org/r/20250924-hwmon2-v1-1-fc529865a325@uniontech.com Signed-off-by: Guenter Roeck --- drivers/hwmon/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d6769288a76e..d61014d7968a 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -771,7 +771,7 @@ config SENSORS_GL520SM config SENSORS_GPD tristate "GPD handhelds" - depends on X86 + depends on X86 && DMI && HAS_IOPORT help If you say yes here you get support for fan readings and control over GPD handheld devices. -- cgit v1.2.3 From 1c1658058c99bcfd3b2347e587a556986037f80a Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 17 Sep 2025 20:10:35 +0200 Subject: hwmon: (dell-smm) Add support for automatic fan mode Many machines treat fan state 3 as some sort of automatic mode, which is superior to the separate SMM calls for switching to automatic fan mode for two reasons: - the fan control mode can be controlled for each fan separately - the current fan control mode can be retrieved from the BIOS On some machines however, this special fan state does not exist. Fan state 3 acts like a regular fan state on such machines or does not exist at all. Such machines usually use separate SMM calls for enabling/disabling automatic fan control. Add support for it. If the machine supports separate SMM calls for changing the fan control mode, then the other interface is ignored. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250917181036.10972-4-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- Documentation/hwmon/dell-smm-hwmon.rst | 56 +++++++++++++++---------- drivers/hwmon/dell-smm-hwmon.c | 74 ++++++++++++++++++++++++++++------ include/uapi/linux/i8k.h | 2 + 3 files changed, 98 insertions(+), 34 deletions(-) diff --git a/Documentation/hwmon/dell-smm-hwmon.rst b/Documentation/hwmon/dell-smm-hwmon.rst index 5a4edb6565cf..3e4e2d916ac5 100644 --- a/Documentation/hwmon/dell-smm-hwmon.rst +++ b/Documentation/hwmon/dell-smm-hwmon.rst @@ -38,7 +38,7 @@ fan[1-4]_min RO Minimal Fan speed in RPM fan[1-4]_max RO Maximal Fan speed in RPM fan[1-4]_target RO Expected Fan speed in RPM pwm[1-4] RW Control the fan PWM duty-cycle. -pwm1_enable WO Enable or disable automatic BIOS fan +pwm[1-4]_enable RW/WO Enable or disable automatic BIOS fan control (not supported on all laptops, see below for details). temp[1-10]_input RO Temperature reading in milli-degrees @@ -49,26 +49,40 @@ temp[1-10]_label RO Temperature sensor label. Due to the nature of the SMM interface, each pwmX attribute controls fan number X. -Disabling automatic BIOS fan control ------------------------------------- - -On some laptops the BIOS automatically sets fan speed every few -seconds. Therefore the fan speed set by mean of this driver is quickly -overwritten. - -There is experimental support for disabling automatic BIOS fan -control, at least on laptops where the corresponding SMM command is -known, by writing the value ``1`` in the attribute ``pwm1_enable`` -(writing ``2`` enables automatic BIOS control again). Even if you have -more than one fan, all of them are set to either enabled or disabled -automatic fan control at the same time and, notwithstanding the name, -``pwm1_enable`` sets automatic control for all fans. - -If ``pwm1_enable`` is not available, then it means that SMM codes for -enabling and disabling automatic BIOS fan control are not whitelisted -for your hardware. It is possible that codes that work for other -laptops actually work for yours as well, or that you have to discover -new codes. +Enabling/Disabling automatic BIOS fan control +--------------------------------------------- + +There exist two methods for enabling/disabling automatic BIOS fan control: + +1. Separate SMM commands to enable/disable automatic BIOS fan control for all fans. + +2. A special fan state that enables automatic BIOS fan control for a individual fan. + +The driver cannot reliably detect what method should be used on a given +device, so instead the following heuristic is used: + +- use fan state 3 for enabling BIOS fan control if the maximum fan state + setable by the user is smaller than 3 (default setting). + +- use separate SMM commands if device is whitelisted to support them. + +When using the first method, each fan will have a standard ``pwmX_enable`` +sysfs attribute. Writing ``1`` into this attribute will disable automatic +BIOS fan control for the associated fan and set it to maximum speed. Enabling +BIOS fan control again can be achieved by writing ``2`` into this attribute. +Reading this sysfs attributes returns the current setting as reported by +the underlying hardware. + +When using the second method however, only the ``pwm1_enable`` sysfs attribute +will be available to enable/disable automatic BIOS fan control globaly for all +fans available on a given device. Additionally, this sysfs attribute is write-only +as there exists no SMM command for reading the current fan control setting. + +If no ``pwmX_enable`` attributes are available, then it means that the driver +cannot use the first method and the SMM codes for enabling and disabling automatic +BIOS fan control are not whitelisted for your device. It is possible that codes +that work for other laptops actually work for yours as well, or that you have to +discover new codes. Check the list ``i8k_whitelist_fan_control`` in file ``drivers/hwmon/dell-smm-hwmon.c`` in the kernel tree: as a first diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 36576db09706..79befa13b699 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -764,6 +764,13 @@ static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned l if (ret < 0) return ret; + /* + * A fan state bigger than i8k_fan_max might indicate that + * the fan is currently in automatic mode. + */ + if (ret > cdata->data->i8k_fan_max) + return -ENODATA; + *state = ret; return 0; @@ -851,7 +858,14 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types break; case hwmon_pwm_enable: - if (auto_fan) + if (auto_fan) { + /* + * The setting affects all fans, so only create a + * single attribute. + */ + if (channel != 1) + return 0; + /* * There is no command for retrieve the current status * from BIOS, and userspace/firmware itself can change @@ -859,6 +873,10 @@ static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types * Thus we can only provide write-only access for now. */ return 0200; + } + + if (data->fan[channel] && data->i8k_fan_max < I8K_FAN_AUTO) + return 0644; break; default: @@ -928,14 +946,28 @@ static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 a } break; case hwmon_pwm: + ret = i8k_get_fan_status(data, channel); + if (ret < 0) + return ret; + switch (attr) { case hwmon_pwm_input: - ret = i8k_get_fan_status(data, channel); - if (ret < 0) - return ret; + /* + * A fan state bigger than i8k_fan_max might indicate that + * the fan is currently in automatic mode. + */ + if (ret > data->i8k_fan_max) + return -ENODATA; *val = clamp_val(ret * data->i8k_pwm_mult, 0, 255); + return 0; + case hwmon_pwm_enable: + if (ret == I8K_FAN_AUTO) + *val = 2; + else + *val = 1; + return 0; default: break; @@ -1022,16 +1054,32 @@ static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 return 0; case hwmon_pwm_enable: - if (!val) - return -EINVAL; - - if (val == 1) + switch (val) { + case 1: enable = false; - else + break; + case 2: enable = true; + break; + default: + return -EINVAL; + } mutex_lock(&data->i8k_mutex); - err = i8k_enable_fan_auto_mode(data, enable); + if (auto_fan) { + err = i8k_enable_fan_auto_mode(data, enable); + } else { + /* + * When putting the fan into manual control mode we have to ensure + * that the device does not overheat until the userspace fan control + * software takes over. Because of this we set the fan speed to + * i8k_fan_max when disabling automatic fan control. + */ + if (enable) + err = i8k_set_fan(data, channel, I8K_FAN_AUTO); + else + err = i8k_set_fan(data, channel, data->i8k_fan_max); + } mutex_unlock(&data->i8k_mutex); if (err < 0) @@ -1082,9 +1130,9 @@ static const struct hwmon_channel_info * const dell_smm_info[] = { ), HWMON_CHANNEL_INFO(pwm, HWMON_PWM_INPUT | HWMON_PWM_ENABLE, - HWMON_PWM_INPUT, - HWMON_PWM_INPUT, - HWMON_PWM_INPUT + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE ), NULL }; diff --git a/include/uapi/linux/i8k.h b/include/uapi/linux/i8k.h index 268e6268f6c8..a16e4049710f 100644 --- a/include/uapi/linux/i8k.h +++ b/include/uapi/linux/i8k.h @@ -36,6 +36,8 @@ #define I8K_FAN_LOW 1 #define I8K_FAN_HIGH 2 #define I8K_FAN_TURBO 3 +/* Many machines treat this mode as some sort of automatic mode */ +#define I8K_FAN_AUTO 3 #define I8K_FAN_MAX I8K_FAN_TURBO #define I8K_VOL_UP 1 -- cgit v1.2.3 From 53d3bd48ef6ff1567a75ca77728968f5ab493cb4 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 17 Sep 2025 20:10:36 +0200 Subject: hwmon: (dell-smm) Add support for Dell OptiPlex 7040 The Dell OptiPlex 7040 supports the legacy SMM interface for reading sensors and performing fan control. Whitelist this machine so that this driver loads automatically. Closes: https://github.com/Wer-Wolf/i8kutils/issues/15 Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250917181036.10972-5-W_Armin@gmx.de Signed-off-by: Guenter Roeck --- drivers/hwmon/dell-smm-hwmon.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c index 79befa13b699..cbe1a74a3dee 100644 --- a/drivers/hwmon/dell-smm-hwmon.c +++ b/drivers/hwmon/dell-smm-hwmon.c @@ -1330,6 +1330,13 @@ static const struct dmi_system_id i8k_dmi_table[] __initconst = { DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7050"), }, }, + { + .ident = "Dell OptiPlex 7040", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7040"), + }, + }, { .ident = "Dell Precision", .matches = { -- cgit v1.2.3 From 31c092baea5a1c5473d2183c2f2a36c76d083c59 Mon Sep 17 00:00:00 2001 From: Mohamad Kamal Date: Sun, 14 Sep 2025 10:40:10 +0200 Subject: hwmon: (asus-ec-sensors) add TUF GAMING X670E PLUS WIFI Add support for TUF GAMING X670E PLUS WIFI Signed-off-by: Mohamad Kamal Signed-off-by: Eugene Shalygin Link: https://lore.kernel.org/r/20250914084019.1108941-1-eugene.shalygin@gmail.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/asus_ec_sensors.rst | 1 + drivers/hwmon/asus-ec-sensors.c | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Documentation/hwmon/asus_ec_sensors.rst b/Documentation/hwmon/asus_ec_sensors.rst index 5801bc4279c0..a5a58c00c322 100644 --- a/Documentation/hwmon/asus_ec_sensors.rst +++ b/Documentation/hwmon/asus_ec_sensors.rst @@ -46,6 +46,7 @@ Supported boards: * ROG ZENITH II EXTREME * ROG ZENITH II EXTREME ALPHA * TUF GAMING X670E PLUS + * TUF GAMING X670E PLUS WIFI Authors: - Eugene Shalygin diff --git a/drivers/hwmon/asus-ec-sensors.c b/drivers/hwmon/asus-ec-sensors.c index e27a7b08e7b7..34a8f6b834c9 100644 --- a/drivers/hwmon/asus-ec-sensors.c +++ b/drivers/hwmon/asus-ec-sensors.c @@ -805,6 +805,8 @@ static const struct dmi_system_id dmi_table[] = { &board_info_zenith_ii_extreme), DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS", &board_info_tuf_gaming_x670e_plus), + DMI_EXACT_MATCH_ASUS_BOARD_NAME("TUF GAMING X670E-PLUS WIFI", + &board_info_tuf_gaming_x670e_plus), {}, }; -- cgit v1.2.3 From 0f6eae86e626e0561d2f545a3183be2e12108410 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 12 Sep 2025 14:07:42 +0200 Subject: dt-bindings: hwmon: sl28cpld: add sa67mcu compatible The Kontron SMARC-sAM67 module features an on-board house keeping uC. It is designed to be compatible with the older sl28cpld implementation, but has different sensors, like voltage and temperature monitoring. Add a new compatible for that board. Signed-off-by: Michael Walle Acked-by: Krzysztof Kozlowski Link: https://lore.kernel.org/r/20250912120745.2295115-5-mwalle@kernel.org Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml b/Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml index 010333cb25c0..ec20625db454 100644 --- a/Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml +++ b/Documentation/devicetree/bindings/hwmon/kontron,sl28cpld-hwmon.yaml @@ -16,6 +16,7 @@ description: | properties: compatible: enum: + - kontron,sa67mcu-hwmon - kontron,sl28cpld-fan reg: -- cgit v1.2.3 From 443b39c82c322c9f3c38bea0389fe927ba00b3b4 Mon Sep 17 00:00:00 2001 From: Michael Walle Date: Fri, 12 Sep 2025 14:07:44 +0200 Subject: hwmon: add SMARC-sAM67 support Add a new driver for the Kontron SMARC-sAM67 board management controller. It has two voltage sensors and one temperature sensor. Signed-off-by: Michael Walle Link: https://lore.kernel.org/r/20250912120745.2295115-7-mwalle@kernel.org [groeck: Added sa67 to index.rst] Signed-off-by: Guenter Roeck --- Documentation/hwmon/index.rst | 1 + Documentation/hwmon/sa67.rst | 41 +++++++++++ MAINTAINERS | 1 + drivers/hwmon/Kconfig | 10 +++ drivers/hwmon/Makefile | 1 + drivers/hwmon/sa67mcu-hwmon.c | 161 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 Documentation/hwmon/sa67.rst create mode 100644 drivers/hwmon/sa67mcu-hwmon.c diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst index 1803a43f6216..51a5bdf75b08 100644 --- a/Documentation/hwmon/index.rst +++ b/Documentation/hwmon/index.rst @@ -214,6 +214,7 @@ Hardware Monitoring Kernel Drivers q54sj108a2 qnap-mcu-hwmon raspberrypi-hwmon + sa67 sbrmi sbtsi_temp sch5627 diff --git a/Documentation/hwmon/sa67.rst b/Documentation/hwmon/sa67.rst new file mode 100644 index 000000000000..029c7c169b7f --- /dev/null +++ b/Documentation/hwmon/sa67.rst @@ -0,0 +1,41 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +Kernel driver sa67mcu +===================== + +Supported chips: + + * Kontron sa67mcu + + Prefix: 'sa67mcu' + + Datasheet: not available + +Authors: Michael Walle + +Description +----------- + +The sa67mcu is a board management controller which also exposes a hardware +monitoring controller. + +The controller has two voltage and one temperature sensor. The values are +hold in two 8 bit registers to form one 16 bit value. Reading the lower byte +will also capture the high byte to make the access atomic. The unit of the +volatge sensors are 1mV and the unit of the temperature sensor is 0.1degC. + +Sysfs entries +------------- + +The following attributes are supported. + +======================= ======================================================== +in0_label "VDDIN" +in0_input Measured VDDIN voltage. + +in1_label "VDD_RTC" +in1_input Measured VDD_RTC voltage. + +temp1_input MCU temperature. Roughly the board temperature. +======================= ======================================================== + diff --git a/MAINTAINERS b/MAINTAINERS index 5642fd647326..8e314837eaa4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -23231,6 +23231,7 @@ F: Documentation/devicetree/bindings/mfd/kontron,sl28cpld.yaml F: Documentation/devicetree/bindings/pwm/kontron,sl28cpld-pwm.yaml F: Documentation/devicetree/bindings/watchdog/kontron,sl28cpld-wdt.yaml F: drivers/gpio/gpio-sl28cpld.c +F: drivers/hwmon/sa67mcu-hwmon.c F: drivers/hwmon/sl28cpld-hwmon.c F: drivers/irqchip/irq-sl28cpld.c F: drivers/pwm/pwm-sl28cpld.c diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index d61014d7968a..374590fbadac 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -1905,6 +1905,16 @@ config SENSORS_RASPBERRYPI_HWMON This driver can also be built as a module. If so, the module will be called raspberrypi-hwmon. +config SENSORS_SA67MCU + tristate "Kontron sa67mcu hardware monitoring driver" + depends on MFD_SL28CPLD || COMPILE_TEST + help + If you say yes here you get support for the voltage and temperature + monitor of the sa67 board management controller. + + This driver can also be built as a module. If so, the module + will be called sa67mcu-hwmon. + config SENSORS_SL28CPLD tristate "Kontron sl28cpld hardware monitoring driver" depends on MFD_SL28CPLD || COMPILE_TEST diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 051981eb8a50..2956898776bb 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -197,6 +197,7 @@ obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o +obj-$(CONFIG_SENSORS_SA67MCU) += sa67mcu-hwmon.o obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o diff --git a/drivers/hwmon/sa67mcu-hwmon.c b/drivers/hwmon/sa67mcu-hwmon.c new file mode 100644 index 000000000000..22f703b7b256 --- /dev/null +++ b/drivers/hwmon/sa67mcu-hwmon.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * sl67mcu hardware monitoring driver + * + * Copyright 2025 Kontron Europe GmbH + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SA67MCU_VOLTAGE(n) (0x00 + ((n) * 2)) +#define SA67MCU_TEMP(n) (0x04 + ((n) * 2)) + +struct sa67mcu_hwmon { + struct regmap *regmap; + u32 offset; +}; + +static int sa67mcu_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, long *input) +{ + struct sa67mcu_hwmon *hwmon = dev_get_drvdata(dev); + unsigned int offset; + u8 reg[2]; + int ret; + + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_input: + offset = hwmon->offset + SA67MCU_VOLTAGE(channel); + break; + default: + return -EOPNOTSUPP; + } + break; + case hwmon_temp: + switch (attr) { + case hwmon_temp_input: + offset = hwmon->offset + SA67MCU_TEMP(channel); + break; + default: + return -EOPNOTSUPP; + } + break; + default: + return -EOPNOTSUPP; + } + + /* Reading the low byte will capture the value */ + ret = regmap_bulk_read(hwmon->regmap, offset, reg, ARRAY_SIZE(reg)); + if (ret) + return ret; + + *input = reg[1] << 8 | reg[0]; + + /* Temperatures are s16 and in 0.1degC steps. */ + if (type == hwmon_temp) + *input = sign_extend32(*input, 15) * 100; + + return 0; +} + +static const struct hwmon_channel_info * const sa67mcu_hwmon_info[] = { + HWMON_CHANNEL_INFO(in, + HWMON_I_INPUT | HWMON_I_LABEL, + HWMON_I_INPUT | HWMON_I_LABEL), + HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT), + NULL +}; + +static const char *const sa67mcu_hwmon_in_labels[] = { + "VDDIN", + "VDD_RTC", +}; + +static int sa67mcu_hwmon_read_string(struct device *dev, + enum hwmon_sensor_types type, u32 attr, + int channel, const char **str) +{ + switch (type) { + case hwmon_in: + switch (attr) { + case hwmon_in_label: + *str = sa67mcu_hwmon_in_labels[channel]; + return 0; + default: + return -EOPNOTSUPP; + } + default: + return -EOPNOTSUPP; + } +} + +static const struct hwmon_ops sa67mcu_hwmon_ops = { + .visible = 0444, + .read = sa67mcu_hwmon_read, + .read_string = sa67mcu_hwmon_read_string, +}; + +static const struct hwmon_chip_info sa67mcu_hwmon_chip_info = { + .ops = &sa67mcu_hwmon_ops, + .info = sa67mcu_hwmon_info, +}; + +static int sa67mcu_hwmon_probe(struct platform_device *pdev) +{ + struct sa67mcu_hwmon *hwmon; + struct device *hwmon_dev; + int ret; + + if (!pdev->dev.parent) + return -ENODEV; + + hwmon = devm_kzalloc(&pdev->dev, sizeof(*hwmon), GFP_KERNEL); + if (!hwmon) + return -ENOMEM; + + hwmon->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!hwmon->regmap) + return -ENODEV; + + ret = device_property_read_u32(&pdev->dev, "reg", &hwmon->offset); + if (ret) + return -EINVAL; + + hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev, + "sa67mcu_hwmon", hwmon, + &sa67mcu_hwmon_chip_info, + NULL); + if (IS_ERR(hwmon_dev)) + dev_err(&pdev->dev, "failed to register as hwmon device"); + + return PTR_ERR_OR_ZERO(hwmon_dev); +} + +static const struct of_device_id sa67mcu_hwmon_of_match[] = { + { .compatible = "kontron,sa67mcu-hwmon", }, + {} +}; +MODULE_DEVICE_TABLE(of, sa67mcu_hwmon_of_match); + +static struct platform_driver sa67mcu_hwmon_driver = { + .probe = sa67mcu_hwmon_probe, + .driver = { + .name = "sa67mcu-hwmon", + .of_match_table = sa67mcu_hwmon_of_match, + }, +}; +module_platform_driver(sa67mcu_hwmon_driver); + +MODULE_DESCRIPTION("sa67mcu Hardware Monitoring Driver"); +MODULE_AUTHOR("Michael Walle "); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 60ac65a31041b0e5dfd736a79027314b9d533ef5 Mon Sep 17 00:00:00 2001 From: Sung-Chi Li Date: Thu, 11 Sep 2025 06:56:34 +0000 Subject: platform/chrome: update pwm fan control host commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update cros_ec_commands.h to include definitions for getting PWM fan duty, getting and setting the fan control mode. Signed-off-by: Sung-Chi Li Acked-by: Tzung-Bi Shih Reviewed-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20250911-cros_ec_fan-v6-1-a1446cc098af@google.com Signed-off-by: Guenter Roeck --- include/linux/platform_data/cros_ec_commands.h | 29 +++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/include/linux/platform_data/cros_ec_commands.h b/include/linux/platform_data/cros_ec_commands.h index c19b404e3d8d..69294f79cc88 100644 --- a/include/linux/platform_data/cros_ec_commands.h +++ b/include/linux/platform_data/cros_ec_commands.h @@ -1825,6 +1825,16 @@ struct ec_response_pwm_get_duty { uint16_t duty; /* Duty cycle, EC_PWM_MAX_DUTY = 100% */ } __ec_align2; +#define EC_CMD_PWM_GET_FAN_DUTY 0x0027 + +struct ec_params_pwm_get_fan_duty { + uint8_t fan_idx; +} __ec_align1; + +struct ec_response_pwm_get_fan_duty { + uint32_t percent; /* Percentage of duty cycle, ranging from 0 ~ 100 */ +} __ec_align4; + /*****************************************************************************/ /* * Lightbar commands. This looks worse than it is. Since we only use one HOST @@ -3127,14 +3137,31 @@ struct ec_params_thermal_set_threshold_v1 { /****************************************************************************/ -/* Toggle automatic fan control */ +/* Set or get fan control mode */ #define EC_CMD_THERMAL_AUTO_FAN_CTRL 0x0052 +enum ec_auto_fan_ctrl_cmd { + EC_AUTO_FAN_CONTROL_CMD_SET = 0, + EC_AUTO_FAN_CONTROL_CMD_GET, +}; + /* Version 1 of input params */ struct ec_params_auto_fan_ctrl_v1 { uint8_t fan_idx; } __ec_align1; +/* Version 2 of input params */ +struct ec_params_auto_fan_ctrl_v2 { + uint8_t fan_idx; + uint8_t cmd; /* enum ec_auto_fan_ctrl_cmd */ + uint8_t set_auto; /* only used with EC_AUTO_FAN_CONTROL_CMD_SET - bool + */ +} __ec_align4; + +struct ec_response_auto_fan_control { + uint8_t is_auto; /* bool */ +} __ec_align1; + /* Get/Set TMP006 calibration data */ #define EC_CMD_TMP006_GET_CALIBRATION 0x0053 #define EC_CMD_TMP006_SET_CALIBRATION 0x0054 -- cgit v1.2.3 From fb8e659309f72e54ed011c6bfe98597b9236805d Mon Sep 17 00:00:00 2001 From: Sung-Chi Li Date: Thu, 11 Sep 2025 06:56:35 +0000 Subject: hwmon: (cros_ec) add PWM control over fans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Newer EC firmware supports controlling fans through host commands, so adding corresponding implementations for controlling these fans in the driver for other kernel services and userspace to control them. The driver will first probe the supported host command versions (get and set of fan PWM values, get and set of fan control mode) to see if the connected EC fulfills the requirements of controlling the fan, then exposes corresponding sysfs nodes for userspace to control the fan with corresponding read and write implementations. As EC will automatically change the fan mode to auto when the device is suspended, the power management hooks are added as well to keep the fan control mode and fan PWM value consistent during suspend and resume. As we need to access the hwmon device in the power management hook, update the driver by storing the hwmon device in the driver data as well. Signed-off-by: Sung-Chi Li Acked-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20250911-cros_ec_fan-v6-2-a1446cc098af@google.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/cros_ec_hwmon.rst | 5 +- drivers/hwmon/cros_ec_hwmon.c | 230 ++++++++++++++++++++++++++++++++++ 2 files changed, 234 insertions(+), 1 deletion(-) diff --git a/Documentation/hwmon/cros_ec_hwmon.rst b/Documentation/hwmon/cros_ec_hwmon.rst index 47ecae983bdb..355557a08c9a 100644 --- a/Documentation/hwmon/cros_ec_hwmon.rst +++ b/Documentation/hwmon/cros_ec_hwmon.rst @@ -23,4 +23,7 @@ ChromeOS embedded controller used in Chromebooks and other devices. The channel labels exposed via hwmon are retrieved from the EC itself. -Fan and temperature readings are supported. +Fan and temperature readings are supported. PWM fan control is also supported if +the EC also supports setting fan PWM values and fan mode. Note that EC will +switch fan control mode back to auto when suspended. This driver will restore +the fan state to what they were before suspended when resumed. diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 9991c3fa020a..9eddc554ddef 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -17,10 +18,17 @@ #define DRV_NAME "cros-ec-hwmon" +#define CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION 0 +#define CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION 1 +#define CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION 2 + struct cros_ec_hwmon_priv { struct cros_ec_device *cros_ec; const char *temp_sensor_names[EC_TEMP_SENSOR_ENTRIES + EC_TEMP_SENSOR_B_ENTRIES]; u8 usable_fans; + bool fan_control_supported; + u8 manual_fans; /* bits to indicate whether the fan is set to manual */ + u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES]; }; static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed) @@ -36,6 +44,42 @@ static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index return 0; } +static int cros_ec_hwmon_read_pwm_value(struct cros_ec_device *cros_ec, u8 index, u8 *pwm_value) +{ + struct ec_params_pwm_get_fan_duty req = { + .fan_idx = index, + }; + struct ec_response_pwm_get_fan_duty resp; + int ret; + + ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION, + EC_CMD_PWM_GET_FAN_DUTY, &req, sizeof(req), &resp, sizeof(resp)); + if (ret < 0) + return ret; + + *pwm_value = (u8)DIV_ROUND_CLOSEST(le32_to_cpu(resp.percent) * 255, 100); + return 0; +} + +static int cros_ec_hwmon_read_pwm_enable(struct cros_ec_device *cros_ec, u8 index, + u8 *control_method) +{ + struct ec_params_auto_fan_ctrl_v2 req = { + .cmd = EC_AUTO_FAN_CONTROL_CMD_GET, + .fan_idx = index, + }; + struct ec_response_auto_fan_control resp; + int ret; + + ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION, + EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), &resp, sizeof(resp)); + if (ret < 0) + return ret; + + *control_method = resp.is_auto ? 2 : 1; + return 0; +} + static int cros_ec_hwmon_read_temp(struct cros_ec_device *cros_ec, u8 index, u8 *temp) { unsigned int offset; @@ -75,6 +119,8 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, { struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev); int ret = -EOPNOTSUPP; + u8 control_method; + u8 pwm_value; u16 speed; u8 temp; @@ -92,6 +138,17 @@ static int cros_ec_hwmon_read(struct device *dev, enum hwmon_sensor_types type, if (ret == 0) *val = cros_ec_hwmon_is_error_fan(speed); } + } else if (type == hwmon_pwm) { + if (attr == hwmon_pwm_enable) { + ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, channel, + &control_method); + if (ret == 0) + *val = control_method; + } else if (attr == hwmon_pwm_input) { + ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, channel, &pwm_value); + if (ret == 0) + *val = pwm_value; + } } else if (type == hwmon_temp) { if (attr == hwmon_temp_input) { ret = cros_ec_hwmon_read_temp(priv->cros_ec, channel, &temp); @@ -124,6 +181,74 @@ static int cros_ec_hwmon_read_string(struct device *dev, enum hwmon_sensor_types return -EOPNOTSUPP; } +static int cros_ec_hwmon_set_fan_pwm_val(struct cros_ec_device *cros_ec, u8 index, u8 val) +{ + struct ec_params_pwm_set_fan_duty_v1 req = { + .fan_idx = index, + .percent = DIV_ROUND_CLOSEST((uint32_t)val * 100, 255), + }; + int ret; + + ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION, + EC_CMD_PWM_SET_FAN_DUTY, &req, sizeof(req), NULL, 0); + if (ret < 0) + return ret; + return 0; +} + +static int cros_ec_hwmon_write_pwm_input(struct cros_ec_device *cros_ec, u8 index, u8 val) +{ + u8 control_method; + int ret; + + ret = cros_ec_hwmon_read_pwm_enable(cros_ec, index, &control_method); + if (ret) + return ret; + if (control_method != 1) + return -EOPNOTSUPP; + + return cros_ec_hwmon_set_fan_pwm_val(cros_ec, index, val); +} + +static int cros_ec_hwmon_write_pwm_enable(struct cros_ec_device *cros_ec, u8 index, u8 val) +{ + struct ec_params_auto_fan_ctrl_v2 req = { + .fan_idx = index, + .cmd = EC_AUTO_FAN_CONTROL_CMD_SET, + }; + int ret; + + /* No CrOS EC supports no fan speed control */ + if (val == 0) + return -EOPNOTSUPP; + + req.set_auto = (val != 1) ? true : false; + ret = cros_ec_cmd(cros_ec, CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION, + EC_CMD_THERMAL_AUTO_FAN_CTRL, &req, sizeof(req), NULL, 0); + if (ret < 0) + return ret; + return 0; +} + +static int cros_ec_hwmon_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, + int channel, long val) +{ + struct cros_ec_hwmon_priv *priv = dev_get_drvdata(dev); + + if (type == hwmon_pwm) { + switch (attr) { + case hwmon_pwm_input: + return cros_ec_hwmon_write_pwm_input(priv->cros_ec, channel, val); + case hwmon_pwm_enable: + return cros_ec_hwmon_write_pwm_enable(priv->cros_ec, channel, val); + default: + return -EOPNOTSUPP; + } + } + + return -EOPNOTSUPP; +} + static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, u32 attr, int channel) { @@ -132,6 +257,9 @@ static umode_t cros_ec_hwmon_is_visible(const void *data, enum hwmon_sensor_type if (type == hwmon_fan) { if (priv->usable_fans & BIT(channel)) return 0444; + } else if (type == hwmon_pwm) { + if (priv->fan_control_supported && priv->usable_fans & BIT(channel)) + return 0644; } else if (type == hwmon_temp) { if (priv->temp_sensor_names[channel]) return 0444; @@ -147,6 +275,11 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = { HWMON_F_INPUT | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_FAULT, HWMON_F_INPUT | HWMON_F_FAULT), + HWMON_CHANNEL_INFO(pwm, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE, + HWMON_PWM_INPUT | HWMON_PWM_ENABLE), HWMON_CHANNEL_INFO(temp, HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL, HWMON_T_INPUT | HWMON_T_FAULT | HWMON_T_LABEL, @@ -178,6 +311,7 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = { static const struct hwmon_ops cros_ec_hwmon_ops = { .read = cros_ec_hwmon_read, .read_string = cros_ec_hwmon_read_string, + .write = cros_ec_hwmon_write, .is_visible = cros_ec_hwmon_is_visible, }; @@ -233,6 +367,25 @@ static void cros_ec_hwmon_probe_fans(struct cros_ec_hwmon_priv *priv) } } +static inline bool is_cros_ec_cmd_available(struct cros_ec_device *cros_ec, + u16 cmd, u8 version) +{ + int ret; + + ret = cros_ec_get_cmd_versions(cros_ec, cmd); + return ret >= 0 && (ret & EC_VER_MASK(version)); +} + +static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cros_ec) +{ + return is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_GET_FAN_DUTY, + CROS_EC_HWMON_PWM_GET_FAN_DUTY_CMD_VERSION) && + is_cros_ec_cmd_available(cros_ec, EC_CMD_PWM_SET_FAN_DUTY, + CROS_EC_HWMON_PWM_SET_FAN_DUTY_CMD_VERSION) && + is_cros_ec_cmd_available(cros_ec, EC_CMD_THERMAL_AUTO_FAN_CTRL, + CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION); +} + static int cros_ec_hwmon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -259,13 +412,88 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev) cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version); cros_ec_hwmon_probe_fans(priv); + priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec); hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv, &cros_ec_hwmon_chip_info, NULL); + platform_set_drvdata(pdev, priv); return PTR_ERR_OR_ZERO(hwmon_dev); } +static int cros_ec_hwmon_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev); + u8 control_method; + size_t i; + int ret; + + if (!priv->fan_control_supported) + return 0; + + /* EC sets fan control to auto after suspended, store settings before suspending. */ + for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (!(priv->usable_fans & BIT(i))) + continue; + + ret = cros_ec_hwmon_read_pwm_enable(priv->cros_ec, i, &control_method); + if (ret) { + dev_warn(&pdev->dev, "failed to get mode setting for fan %zu: %d\n", i, + ret); + continue; + } + + if (control_method != 1) { + priv->manual_fans &= ~BIT(i); + continue; + } else { + priv->manual_fans |= BIT(i); + } + + ret = cros_ec_hwmon_read_pwm_value(priv->cros_ec, i, &priv->manual_fan_pwm[i]); + /* + * If storing the value failed, invalidate the stored mode value by setting it + * to auto control. EC will automatically switch to auto mode for that fan after + * suspended. + */ + if (ret) { + dev_warn(&pdev->dev, "failed to get PWM setting for fan %zu: %pe\n", i, + ERR_PTR(ret)); + priv->manual_fans &= ~BIT(i); + continue; + } + } + + return 0; +} + +static int cros_ec_hwmon_resume(struct platform_device *pdev) +{ + const struct cros_ec_hwmon_priv *priv = platform_get_drvdata(pdev); + size_t i; + int ret; + + if (!priv->fan_control_supported) + return 0; + + /* EC sets fan control to auto after suspend, restore to settings before suspend. */ + for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (!(priv->manual_fans & BIT(i))) + continue; + + /* + * Setting fan PWM value to EC will change the mode to manual for that fan in EC as + * well, so we do not need to issue a separate fan mode to manual call. + */ + ret = cros_ec_hwmon_set_fan_pwm_val(priv->cros_ec, i, priv->manual_fan_pwm[i]); + if (ret) + dev_warn(&pdev->dev, "failed to restore settings for fan %zu: %pe\n", i, + ERR_PTR(ret)); + } + + return 0; +} + static const struct platform_device_id cros_ec_hwmon_id[] = { { DRV_NAME, 0 }, {} @@ -274,6 +502,8 @@ static const struct platform_device_id cros_ec_hwmon_id[] = { static struct platform_driver cros_ec_hwmon_driver = { .driver.name = DRV_NAME, .probe = cros_ec_hwmon_probe, + .suspend = pm_ptr(cros_ec_hwmon_suspend), + .resume = pm_ptr(cros_ec_hwmon_resume), .id_table = cros_ec_hwmon_id, }; module_platform_driver(cros_ec_hwmon_driver); -- cgit v1.2.3 From 5798b62867b47b6ace287d31172ce748ad70d869 Mon Sep 17 00:00:00 2001 From: Sung-Chi Li Date: Thu, 11 Sep 2025 06:56:36 +0000 Subject: hwmon: (cros_ec) register fans into thermal framework cooling devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Register fans connected under EC as thermal cooling devices as well, so these fans can then work with the thermal framework. During the driver probing phase, we will also try to register each fan as a thermal cooling device based on previous probe result (whether the there are fans connected on that channel, and whether EC supports fan control). The basic get max state, get current state, and set current state methods are then implemented as well. Signed-off-by: Sung-Chi Li Acked-by: Thomas Weißschuh Link: https://lore.kernel.org/r/20250911-cros_ec_fan-v6-3-a1446cc098af@google.com Signed-off-by: Guenter Roeck --- Documentation/hwmon/cros_ec_hwmon.rst | 2 + drivers/hwmon/cros_ec_hwmon.c | 83 +++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/Documentation/hwmon/cros_ec_hwmon.rst b/Documentation/hwmon/cros_ec_hwmon.rst index 355557a08c9a..6db812708325 100644 --- a/Documentation/hwmon/cros_ec_hwmon.rst +++ b/Documentation/hwmon/cros_ec_hwmon.rst @@ -27,3 +27,5 @@ Fan and temperature readings are supported. PWM fan control is also supported if the EC also supports setting fan PWM values and fan mode. Note that EC will switch fan control mode back to auto when suspended. This driver will restore the fan state to what they were before suspended when resumed. +If a fan is controllable, this driver will register that fan as a cooling device +in the thermal framework as well. diff --git a/drivers/hwmon/cros_ec_hwmon.c b/drivers/hwmon/cros_ec_hwmon.c index 9eddc554ddef..48331703f2f5 100644 --- a/drivers/hwmon/cros_ec_hwmon.c +++ b/drivers/hwmon/cros_ec_hwmon.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,11 @@ struct cros_ec_hwmon_priv { u8 manual_fan_pwm[EC_FAN_SPEED_ENTRIES]; }; +struct cros_ec_hwmon_cooling_priv { + struct cros_ec_hwmon_priv *hwmon_priv; + u8 index; +}; + static int cros_ec_hwmon_read_fan_speed(struct cros_ec_device *cros_ec, u8 index, u16 *speed) { int ret; @@ -308,6 +314,42 @@ static const struct hwmon_channel_info * const cros_ec_hwmon_info[] = { NULL }; +static int cros_ec_hwmon_cooling_get_max_state(struct thermal_cooling_device *cdev, + unsigned long *val) +{ + *val = 255; + return 0; +} + +static int cros_ec_hwmon_cooling_get_cur_state(struct thermal_cooling_device *cdev, + unsigned long *val) +{ + const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata; + u8 read_val; + int ret; + + ret = cros_ec_hwmon_read_pwm_value(priv->hwmon_priv->cros_ec, priv->index, &read_val); + if (ret) + return ret; + + *val = read_val; + return 0; +} + +static int cros_ec_hwmon_cooling_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long val) +{ + const struct cros_ec_hwmon_cooling_priv *priv = cdev->devdata; + + return cros_ec_hwmon_write_pwm_input(priv->hwmon_priv->cros_ec, priv->index, val); +} + +static const struct thermal_cooling_device_ops cros_ec_thermal_cooling_ops = { + .get_max_state = cros_ec_hwmon_cooling_get_max_state, + .get_cur_state = cros_ec_hwmon_cooling_get_cur_state, + .set_cur_state = cros_ec_hwmon_cooling_set_cur_state, +}; + static const struct hwmon_ops cros_ec_hwmon_ops = { .read = cros_ec_hwmon_read, .read_string = cros_ec_hwmon_read_string, @@ -386,6 +428,46 @@ static bool cros_ec_hwmon_probe_fan_control_supported(struct cros_ec_device *cro CROS_EC_HWMON_THERMAL_AUTO_FAN_CTRL_CMD_VERSION); } +static void cros_ec_hwmon_register_fan_cooling_devices(struct device *dev, + struct cros_ec_hwmon_priv *priv) +{ + struct cros_ec_hwmon_cooling_priv *cpriv; + struct thermal_cooling_device *cdev; + const char *type; + size_t i; + + if (!IS_ENABLED(CONFIG_THERMAL)) + return; + + if (!priv->fan_control_supported) + return; + + for (i = 0; i < EC_FAN_SPEED_ENTRIES; i++) { + if (!(priv->usable_fans & BIT(i))) + continue; + + cpriv = devm_kzalloc(dev, sizeof(*cpriv), GFP_KERNEL); + if (!cpriv) + continue; + + type = devm_kasprintf(dev, GFP_KERNEL, "%s-fan%zu", dev_name(dev), i); + if (!type) { + dev_warn(dev, "no memory to compose cooling device type for fan %zu\n", i); + continue; + } + + cpriv->hwmon_priv = priv; + cpriv->index = i; + cdev = devm_thermal_of_cooling_device_register(dev, NULL, type, cpriv, + &cros_ec_thermal_cooling_ops); + if (IS_ERR(cdev)) { + dev_warn(dev, "failed to register fan %zu as a cooling device: %pe\n", i, + cdev); + continue; + } + } +} + static int cros_ec_hwmon_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -413,6 +495,7 @@ static int cros_ec_hwmon_probe(struct platform_device *pdev) cros_ec_hwmon_probe_temp_sensors(dev, priv, thermal_version); cros_ec_hwmon_probe_fans(priv); priv->fan_control_supported = cros_ec_hwmon_probe_fan_control_supported(priv->cros_ec); + cros_ec_hwmon_register_fan_cooling_devices(dev, priv); hwmon_dev = devm_hwmon_device_register_with_info(dev, "cros_ec", priv, &cros_ec_hwmon_chip_info, NULL); -- cgit v1.2.3 From c02e4644f8ac9c501077ef5ac53ae7fc51472d49 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Mon, 13 Jan 2025 10:48:58 +0200 Subject: hwmon: (mlxreg-fan) Separate methods of fan setting coming from different subsystems Distinct between fan speed setting request coming for hwmon and thermal subsystems. There are fields 'last_hwmon_state' and 'last_thermal_state' in the structure 'mlxreg_fan_pwm', which respectively store the cooling state set by the 'hwmon' and 'thermal' subsystem. The purpose is to make arbitration of fan speed setting. For example, if fan speed required to be not lower than some limit, such setting is to be performed through 'hwmon' subsystem, thus 'thermal' subsystem will not set fan below this limit. Currently, the 'last_thermal_state' is also be updated by 'hwmon' causing cooling state to never be set to a lower value. Eliminate update of 'last_thermal_state', when request is coming from 'hwmon' subsystem. Fixes: da74944d3a46 ("hwmon: (mlxreg-fan) Use pwm attribute for setting fan speed low limit") Signed-off-by: Vadim Pasternak Link: https://lore.kernel.org/r/20250113084859.27064-2-vadimp@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mlxreg-fan.c | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c index c25a54d5b39a..0ba9195c9d71 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -113,8 +113,8 @@ struct mlxreg_fan { int divider; }; -static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, - unsigned long state); +static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state, bool thermal); static int mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, @@ -224,8 +224,9 @@ mlxreg_fan_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, * last thermal state. */ if (pwm->last_hwmon_state >= pwm->last_thermal_state) - return mlxreg_fan_set_cur_state(pwm->cdev, - pwm->last_hwmon_state); + return _mlxreg_fan_set_cur_state(pwm->cdev, + pwm->last_hwmon_state, + false); return 0; } return regmap_write(fan->regmap, pwm->reg, val); @@ -357,9 +358,8 @@ static int mlxreg_fan_get_cur_state(struct thermal_cooling_device *cdev, return 0; } -static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, - unsigned long state) - +static int _mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state, bool thermal) { struct mlxreg_fan_pwm *pwm = cdev->devdata; struct mlxreg_fan *fan = pwm->fan; @@ -369,7 +369,8 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, return -EINVAL; /* Save thermal state. */ - pwm->last_thermal_state = state; + if (thermal) + pwm->last_thermal_state = state; state = max_t(unsigned long, state, pwm->last_hwmon_state); err = regmap_write(fan->regmap, pwm->reg, @@ -381,6 +382,13 @@ static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, return 0; } +static int mlxreg_fan_set_cur_state(struct thermal_cooling_device *cdev, + unsigned long state) + +{ + return _mlxreg_fan_set_cur_state(cdev, state, true); +} + static const struct thermal_cooling_device_ops mlxreg_fan_cooling_ops = { .get_max_state = mlxreg_fan_get_max_state, .get_cur_state = mlxreg_fan_get_cur_state, -- cgit v1.2.3 From 1e11552ee54d10c0b602c76b94db602e2581ce57 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Mon, 13 Jan 2025 10:48:59 +0200 Subject: hwmon: (mlxreg-fan) Add support for new flavour of capability register FAN platform data is common across the various systems, while fan driver should be able to apply only the fan instances relevant to specific system. For example, platform data might contain descriptions for fan1, fan2, ..., fan{n}, while some systems equipped with all 'n' fans, others with less. Also, on some systems fan drawer can be equipped with several tachometers and on others only with one. For detection of the real number of equipped drawers and tachometers special capability registers are used. These registers used to indicate presence of drawers and tachometers through the bitmap. For some new big modular systems this register will provide presence data by counter. Use slot parameter to distinct whether capability register contains bitmask or counter. Signed-off-by: Vadim Pasternak Link: https://lore.kernel.org/r/20250113084859.27064-3-vadimp@nvidia.com Signed-off-by: Guenter Roeck --- drivers/hwmon/mlxreg-fan.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/drivers/hwmon/mlxreg-fan.c b/drivers/hwmon/mlxreg-fan.c index 0ba9195c9d71..137a90dd2075 100644 --- a/drivers/hwmon/mlxreg-fan.c +++ b/drivers/hwmon/mlxreg-fan.c @@ -63,12 +63,14 @@ struct mlxreg_fan; * @reg: register offset; * @mask: fault mask; * @prsnt: present register offset; + * @shift: tacho presence bit shift; */ struct mlxreg_fan_tacho { bool connected; u32 reg; u32 mask; u32 prsnt; + u32 shift; }; /* @@ -143,8 +145,10 @@ mlxreg_fan_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, /* * Map channel to presence bit - drawer can be equipped with * one or few FANs, while presence is indicated per drawer. + * Shift channel value if necessary to align with register value. */ - if (BIT(channel / fan->tachos_per_drwr) & regval) { + if (BIT(rol32(channel, tacho->shift) / fan->tachos_per_drwr) & + regval) { /* FAN is not connected - return zero for FAN speed. */ *val = 0; return 0; @@ -408,7 +412,7 @@ static int mlxreg_fan_connect_verify(struct mlxreg_fan *fan, return err; } - return !!(regval & data->bit); + return data->slot ? (data->slot <= regval ? 1 : 0) : !!(regval & data->bit); } static int mlxreg_pwm_connect_verify(struct mlxreg_fan *fan, @@ -545,7 +549,15 @@ static int mlxreg_fan_config(struct mlxreg_fan *fan, return err; } - drwr_avail = hweight32(regval); + /* + * The number of drawers could be specified in registers by counters for newer + * systems, or by bitmasks for older systems. In case the data is provided by + * counter, it is indicated through 'version' field. + */ + if (pdata->version) + drwr_avail = regval; + else + drwr_avail = hweight32(regval); if (!tacho_avail || !drwr_avail || tacho_avail < drwr_avail) { dev_err(fan->dev, "Configuration is invalid: drawers num %d tachos num %d\n", drwr_avail, tacho_avail); -- cgit v1.2.3 From 76bb6969a8cfc5e00ca142fdad86ffd0a6ed9ecd Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 25 Sep 2025 15:22:19 -0400 Subject: dt-bindings: hwmon: (lm75) allow interrupt for ti,tmp75 Allow interrupt for ti,tmp75 because chip has open drain ALERT signal. Signed-off-by: Frank Li Acked-by: Conor Dooley Link: https://lore.kernel.org/r/20250925192219.303825-1-Frank.Li@nxp.com Signed-off-by: Guenter Roeck --- Documentation/devicetree/bindings/hwmon/lm75.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/hwmon/lm75.yaml b/Documentation/devicetree/bindings/hwmon/lm75.yaml index ecdd09a032e5..0b9fda81e3ec 100644 --- a/Documentation/devicetree/bindings/hwmon/lm75.yaml +++ b/Documentation/devicetree/bindings/hwmon/lm75.yaml @@ -70,6 +70,7 @@ allOf: - ti,tmp100 - ti,tmp101 - ti,tmp112 + - ti,tmp75 then: properties: interrupts: false -- cgit v1.2.3