From 3c415b1df95c06ae4f9bdb166541ab366b862cc2 Mon Sep 17 00:00:00 2001 From: Nitin Joshi Date: Tue, 6 May 2025 00:01:52 +0900 Subject: [PATCH 01/16] platform/x86: thinkpad-acpi: Add support for new hotkey for camera shutter switch MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit New Lenovo Thinkpad models, e.g. the 'X9-14 Gen 1' and 'X9-15 Gen 1' has new shortcut on F9 key i.e to switch camera shutter and it send a new 0x131b hkey event when F9 key is pressed. This commit adds support for new hkey 0x131b. Reviewed-by: Mark Pearson Reviewed-by: Hans de Goede Signed-off-by: Nitin Joshi Link: https://lore.kernel.org/r/20250505150152.27968-1-nitjoshi@gmail.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/thinkpad_acpi.c | 43 +++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 9bb191a32e80..bdd33671a79f 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -182,6 +182,7 @@ enum tpacpi_hkey_event_t { * directly in the sparse-keymap. */ TP_HKEY_EV_AMT_TOGGLE = 0x131a, /* Toggle AMT on/off */ + TP_HKEY_EV_CAMERASHUTTER_TOGGLE = 0x131b, /* Toggle Camera Shutter */ TP_HKEY_EV_DOUBLETAP_TOGGLE = 0x131c, /* Toggle trackpoint doubletap on/off */ TP_HKEY_EV_PROFILE_TOGGLE = 0x131f, /* Toggle platform profile in 2024 systems */ TP_HKEY_EV_PROFILE_TOGGLE2 = 0x1401, /* Toggle platform profile in 2025 + systems */ @@ -2250,6 +2251,25 @@ static void tpacpi_input_send_tabletsw(void) } } +#define GCES_NO_SHUTTER_DEVICE BIT(31) + +static int get_camera_shutter(void) +{ + acpi_handle gces_handle; + int output; + + if (ACPI_FAILURE(acpi_get_handle(hkey_handle, "GCES", &gces_handle))) + return -ENODEV; + + if (!acpi_evalf(gces_handle, &output, NULL, "dd", 0)) + return -EIO; + + if (output & GCES_NO_SHUTTER_DEVICE) + return -ENODEV; + + return output; +} + static bool tpacpi_input_send_key(const u32 hkey, bool *send_acpi_ev) { bool known_ev; @@ -3303,7 +3323,7 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) const struct key_entry *keymap; bool radiosw_state = false; bool tabletsw_state = false; - int hkeyv, res, status; + int hkeyv, res, status, camera_shutter_state; vdbg_printk(TPACPI_DBG_INIT | TPACPI_DBG_HKEY, "initializing hotkey subdriver\n"); @@ -3467,6 +3487,12 @@ static int __init hotkey_init(struct ibm_init_struct *iibm) if (res) return res; + camera_shutter_state = get_camera_shutter(); + if (camera_shutter_state >= 0) { + input_set_capability(tpacpi_inputdev, EV_SW, SW_CAMERA_LENS_COVER); + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + } + if (tp_features.hotkey_wlsw) { input_set_capability(tpacpi_inputdev, EV_SW, SW_RFKILL_ALL); input_report_switch(tpacpi_inputdev, @@ -11161,6 +11187,8 @@ static struct platform_driver tpacpi_hwmon_pdriver = { */ static bool tpacpi_driver_event(const unsigned int hkey_event) { + int camera_shutter_state; + switch (hkey_event) { case TP_HKEY_EV_BRGHT_UP: case TP_HKEY_EV_BRGHT_DOWN: @@ -11236,6 +11264,19 @@ static bool tpacpi_driver_event(const unsigned int hkey_event) else dytc_control_amt(!dytc_amt_active); + return true; + case TP_HKEY_EV_CAMERASHUTTER_TOGGLE: + camera_shutter_state = get_camera_shutter(); + if (camera_shutter_state < 0) { + pr_err("Error retrieving camera shutter state after shutter event\n"); + return true; + } + mutex_lock(&tpacpi_inputdev_send_mutex); + + input_report_switch(tpacpi_inputdev, SW_CAMERA_LENS_COVER, camera_shutter_state); + input_sync(tpacpi_inputdev); + + mutex_unlock(&tpacpi_inputdev_send_mutex); return true; case TP_HKEY_EV_DOUBLETAP_TOGGLE: tp_features.trackpoint_doubletap = !tp_features.trackpoint_doubletap; -- 2.51.0 From 53eddae9af0c0b46f9c77a02d23c21c1aa824739 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 May 2025 20:47:32 +0200 Subject: [PATCH 02/16] platform/x86: int3472: Move common.h to public includes, symbols to INTEL_INT3472 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Move the common.h header file to include/linux/platform_data/x86/int3472.h and add a "INTEL_INT3472" kernel-symbol-namespace to the exported symbols. This is a preparation patch for exporting some more symbols for re-use in the atomisp driver. Signed-off-by: Hans de Goede Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250507184737.154747-2-hdegoede@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 1 + drivers/platform/x86/intel/int3472/clk_and_regulator.c | 3 +-- drivers/platform/x86/intel/int3472/common.c | 9 ++++----- drivers/platform/x86/intel/int3472/discrete.c | 4 ++-- drivers/platform/x86/intel/int3472/discrete_quirks.c | 3 +-- drivers/platform/x86/intel/int3472/led.c | 2 +- drivers/platform/x86/intel/int3472/tps68470.c | 3 ++- .../linux/platform_data/x86/int3472.h | 10 +++++++--- 8 files changed, 19 insertions(+), 16 deletions(-) rename drivers/platform/x86/intel/int3472/common.h => include/linux/platform_data/x86/int3472.h (96%) diff --git a/MAINTAINERS b/MAINTAINERS index f9417b4e9baf..08a99fe91bc1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12241,6 +12241,7 @@ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally S: Maintained F: drivers/platform/x86/intel/int3472/ +F: include/linux/platform_data/x86/int3472.h INTEL SPEED SELECT TECHNOLOGY M: Srinivas Pandruvada diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index c85cbfbc16c1..4d00494a7670 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -6,11 +6,10 @@ #include #include #include +#include #include #include -#include "common.h" - /* * 82c0d13a-78c5-4244-9bb1-eb8b539a8d11 * This _DSM GUID allows controlling the sensor clk when it is not controlled diff --git a/drivers/platform/x86/intel/int3472/common.c b/drivers/platform/x86/intel/int3472/common.c index 1638be8fa71e..6dc38d5cbd0b 100644 --- a/drivers/platform/x86/intel/int3472/common.c +++ b/drivers/platform/x86/intel/int3472/common.c @@ -2,10 +2,9 @@ /* Author: Dan Scally */ #include +#include #include -#include "common.h" - union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *id) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -29,7 +28,7 @@ union acpi_object *skl_int3472_get_acpi_buffer(struct acpi_device *adev, char *i return obj; } -EXPORT_SYMBOL_GPL(skl_int3472_get_acpi_buffer); +EXPORT_SYMBOL_NS_GPL(skl_int3472_get_acpi_buffer, "INTEL_INT3472"); int skl_int3472_fill_cldb(struct acpi_device *adev, struct int3472_cldb *cldb) { @@ -53,7 +52,7 @@ out_free_obj: kfree(obj); return ret; } -EXPORT_SYMBOL_GPL(skl_int3472_fill_cldb); +EXPORT_SYMBOL_NS_GPL(skl_int3472_fill_cldb, "INTEL_INT3472"); /* sensor_adev_ret may be NULL, name_ret must not be NULL */ int skl_int3472_get_sensor_adev_and_name(struct device *dev, @@ -84,7 +83,7 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, return ret; } -EXPORT_SYMBOL_GPL(skl_int3472_get_sensor_adev_and_name); +EXPORT_SYMBOL_NS_GPL(skl_int3472_get_sensor_adev_and_name, "INTEL_INT3472"); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Device Driver library"); MODULE_AUTHOR("Daniel Scally "); diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 394975f55d64..d0938da0a591 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -12,12 +12,11 @@ #include #include #include +#include #include #include #include -#include "common.h" - /* * 79234640-9e10-4fea-a5c1-b5aa8b19756f * This _DSM GUID returns information about the GPIO lines mapped to a @@ -479,3 +478,4 @@ module_platform_driver(int3472_discrete); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI Discrete Device Driver"); MODULE_AUTHOR("Daniel Scally "); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("INTEL_INT3472"); diff --git a/drivers/platform/x86/intel/int3472/discrete_quirks.c b/drivers/platform/x86/intel/int3472/discrete_quirks.c index bf88863803b2..552869ef91ab 100644 --- a/drivers/platform/x86/intel/int3472/discrete_quirks.c +++ b/drivers/platform/x86/intel/int3472/discrete_quirks.c @@ -2,8 +2,7 @@ /* Author: Hans de Goede */ #include - -#include "common.h" +#include static const struct int3472_discrete_quirks lenovo_miix_510_quirks = { .avdd_second_sensor = "i2c-OVTI2680:00", diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c index 9cbed694e2ca..c5588e143f7d 100644 --- a/drivers/platform/x86/intel/int3472/led.c +++ b/drivers/platform/x86/intel/int3472/led.c @@ -4,7 +4,7 @@ #include #include #include -#include "common.h" +#include static int int3472_pled_set(struct led_classdev *led_cdev, enum led_brightness brightness) diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c index 81ac4c691963..0133405697dc 100644 --- a/drivers/platform/x86/intel/int3472/tps68470.c +++ b/drivers/platform/x86/intel/int3472/tps68470.c @@ -8,10 +8,10 @@ #include #include #include +#include #include #include -#include "common.h" #include "tps68470.h" #define DESIGNED_FOR_CHROMEOS 1 @@ -261,4 +261,5 @@ module_i2c_driver(int3472_tps68470); MODULE_DESCRIPTION("Intel SkyLake INT3472 ACPI TPS68470 Device Driver"); MODULE_AUTHOR("Daniel Scally "); MODULE_LICENSE("GPL v2"); +MODULE_IMPORT_NS("INTEL_INT3472"); MODULE_SOFTDEP("pre: clk-tps68470 tps68470-regulator"); diff --git a/drivers/platform/x86/intel/int3472/common.h b/include/linux/platform_data/x86/int3472.h similarity index 96% rename from drivers/platform/x86/intel/int3472/common.h rename to include/linux/platform_data/x86/int3472.h index 51b818e62a25..4cf02df6f753 100644 --- a/drivers/platform/x86/intel/int3472/common.h +++ b/include/linux/platform_data/x86/int3472.h @@ -1,8 +1,12 @@ /* SPDX-License-Identifier: GPL-2.0 */ -/* Author: Dan Scally */ +/* + * Intel INT3472 ACPI camera sensor power-management support + * + * Author: Dan Scally + */ -#ifndef _INTEL_SKL_INT3472_H -#define _INTEL_SKL_INT3472_H +#ifndef __PLATFORM_DATA_X86_INT3472_H +#define __PLATFORM_DATA_X86_INT3472_H #include #include -- 2.51.0 From 1e5d088a52c207bcef6a43a6f6ffe162c514ed64 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 May 2025 20:47:33 +0200 Subject: [PATCH 03/16] platform/x86: int3472: Stop using devm_gpiod_get() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The intent is to re-use the INT3472 code for parsing Intel camera sensor GPIOs and mapping them to the sensor for the atomisp driver, which currently has duplicate code. On atomisp devices there is no special INT3472 ACPI device, instead the Intel _DSM to get the GPIO type is part of the ACPI device for the sensor itself. To deal with this the mapping is done from ipu_bridge_init() instead of from a platform-device probe() function, there is no device to tie the lifetime of the gpiod_get() calls done by the INT3472 code to. Switch from devm_gpiod_get() to plain gpiod_get() + explicit gpiod_put() calls, to prepare for the code being re-used in the atomisp driver. Signed-off-by: Hans de Goede Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250507184737.154747-3-hdegoede@redhat.com Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/int3472/clk_and_regulator.c | 6 +++++- drivers/platform/x86/intel/int3472/discrete.c | 6 +++++- drivers/platform/x86/intel/int3472/led.c | 1 + include/linux/platform_data/x86/int3472.h | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/clk_and_regulator.c b/drivers/platform/x86/intel/int3472/clk_and_regulator.c index 4d00494a7670..476ec24d3702 100644 --- a/drivers/platform/x86/intel/int3472/clk_and_regulator.c +++ b/drivers/platform/x86/intel/int3472/clk_and_regulator.c @@ -182,6 +182,7 @@ void skl_int3472_unregister_clock(struct int3472_discrete_device *int3472) clkdev_drop(int3472->clock.cl); clk_unregister(int3472->clock.clk); + gpiod_put(int3472->clock.ena_gpio); } int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, @@ -244,12 +245,15 @@ int skl_int3472_register_regulator(struct int3472_discrete_device *int3472, if (IS_ERR(regulator->rdev)) return PTR_ERR(regulator->rdev); + int3472->regulators[int3472->n_regulator_gpios].ena_gpio = gpio; int3472->n_regulator_gpios++; return 0; } void skl_int3472_unregister_regulator(struct int3472_discrete_device *int3472) { - for (int i = 0; i < int3472->n_regulator_gpios; i++) + for (int i = 0; i < int3472->n_regulator_gpios; i++) { regulator_unregister(int3472->regulators[i].rdev); + gpiod_put(int3472->regulators[i].ena_gpio); + } } diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index d0938da0a591..808d75e8deda 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -117,7 +117,7 @@ skl_int3472_gpiod_get_from_temp_lookup(struct int3472_discrete_device *int3472, return ERR_PTR(ret); gpiod_add_lookup_table(lookup); - desc = devm_gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW); + desc = gpiod_get(int3472->dev, con_id, GPIOD_OUT_LOW); gpiod_remove_lookup_table(lookup); return desc; @@ -340,6 +340,10 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, ret = -EINVAL; break; } + + if (ret) + gpiod_put(gpio); + break; default: dev_warn(int3472->dev, diff --git a/drivers/platform/x86/intel/int3472/led.c b/drivers/platform/x86/intel/int3472/led.c index c5588e143f7d..f1d6d7b0cb75 100644 --- a/drivers/platform/x86/intel/int3472/led.c +++ b/drivers/platform/x86/intel/int3472/led.c @@ -56,4 +56,5 @@ void skl_int3472_unregister_pled(struct int3472_discrete_device *int3472) led_remove_lookup(&int3472->pled.lookup); led_classdev_unregister(&int3472->pled.classdev); + gpiod_put(int3472->pled.gpio); } diff --git a/include/linux/platform_data/x86/int3472.h b/include/linux/platform_data/x86/int3472.h index 4cf02df6f753..0a835cc85c67 100644 --- a/include/linux/platform_data/x86/int3472.h +++ b/include/linux/platform_data/x86/int3472.h @@ -99,6 +99,7 @@ struct int3472_gpio_regulator { struct regulator_consumer_supply supply_map[GPIO_REGULATOR_SUPPLY_MAP_COUNT * 2]; char supply_name_upper[GPIO_SUPPLY_NAME_LENGTH]; char regulator_name[GPIO_REGULATOR_NAME_LENGTH]; + struct gpio_desc *ena_gpio; struct regulator_dev *rdev; struct regulator_desc rdesc; }; -- 2.51.0 From 1cfa1bb9b4033e51d3c3f5ed5bf55475e57cc686 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 May 2025 20:47:34 +0200 Subject: [PATCH 04/16] platform/x86: int3472: Export int3472_discrete_parse_crs() MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit At the moment the atomisp has duplicate code for parsing Intel camera sensor GPIOS and calling the special 79234640-9e10-4fea-a5c1-b5aa8b19756f _DSM to get the GPIO type and map it to the sensor. Export int3472_discrete_parse_crs() so that the atomisp driver can reuse the INT3472 code for this. Signed-off-by: Hans de Goede Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250507184737.154747-4-hdegoede@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/int3472/discrete.c | 15 ++++++++++----- include/linux/platform_data/x86/int3472.h | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 808d75e8deda..c706671e2f63 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -363,7 +363,7 @@ static int skl_int3472_handle_gpio_resources(struct acpi_resource *ares, return 1; } -static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) +int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472) { LIST_HEAD(resource_list); int ret; @@ -388,17 +388,22 @@ static int skl_int3472_parse_crs(struct int3472_discrete_device *int3472) return 0; } +EXPORT_SYMBOL_NS_GPL(int3472_discrete_parse_crs, "INTEL_INT3472_DISCRETE"); -static void skl_int3472_discrete_remove(struct platform_device *pdev) +void int3472_discrete_cleanup(struct int3472_discrete_device *int3472) { - struct int3472_discrete_device *int3472 = platform_get_drvdata(pdev); - gpiod_remove_lookup_table(&int3472->gpios); skl_int3472_unregister_clock(int3472); skl_int3472_unregister_pled(int3472); skl_int3472_unregister_regulator(int3472); } +EXPORT_SYMBOL_NS_GPL(int3472_discrete_cleanup, "INTEL_INT3472_DISCRETE"); + +static void skl_int3472_discrete_remove(struct platform_device *pdev) +{ + int3472_discrete_cleanup(platform_get_drvdata(pdev)); +} static int skl_int3472_discrete_probe(struct platform_device *pdev) { @@ -453,7 +458,7 @@ static int skl_int3472_discrete_probe(struct platform_device *pdev) */ INIT_LIST_HEAD(&int3472->gpios.list); - ret = skl_int3472_parse_crs(int3472); + ret = int3472_discrete_parse_crs(int3472); if (ret) { skl_int3472_discrete_remove(pdev); return ret; diff --git a/include/linux/platform_data/x86/int3472.h b/include/linux/platform_data/x86/int3472.h index 0a835cc85c67..89410f0cb73a 100644 --- a/include/linux/platform_data/x86/int3472.h +++ b/include/linux/platform_data/x86/int3472.h @@ -147,6 +147,9 @@ int skl_int3472_get_sensor_adev_and_name(struct device *dev, struct acpi_device **sensor_adev_ret, const char **name_ret); +int int3472_discrete_parse_crs(struct int3472_discrete_device *int3472); +void int3472_discrete_cleanup(struct int3472_discrete_device *int3472); + int skl_int3472_register_gpio_clock(struct int3472_discrete_device *int3472, struct gpio_desc *gpio); int skl_int3472_register_dsm_clock(struct int3472_discrete_device *int3472); -- 2.51.0 From 45adb05473aa4afd6ff19fd0c46c17a6294ae788 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 May 2025 20:47:35 +0200 Subject: [PATCH 05/16] platform/x86: int3472: Remove unused sensor_config struct member MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit sensor_config is not used anywhere and its struct int3472_sensor_config type also is not declared anywhere, drop it. Signed-off-by: Hans de Goede Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250507184737.154747-5-hdegoede@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- include/linux/platform_data/x86/int3472.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/include/linux/platform_data/x86/int3472.h b/include/linux/platform_data/x86/int3472.h index 89410f0cb73a..78276a11c48d 100644 --- a/include/linux/platform_data/x86/int3472.h +++ b/include/linux/platform_data/x86/int3472.h @@ -110,8 +110,6 @@ struct int3472_discrete_device { struct acpi_device *sensor; const char *sensor_name; - const struct int3472_sensor_config *sensor_config; - struct int3472_gpio_regulator regulators[INT3472_MAX_REGULATORS]; struct int3472_clock { -- 2.51.0 From d4860025a3fd9c20a801c2273227906874e76413 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 7 May 2025 20:47:36 +0200 Subject: [PATCH 06/16] platform/x86: int3472: For mt9m114 sensors map powerdown to powerenable MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit mt9m114 atomisp designs declare both reset and powerdown pins in their GPIO type DSM, but the mt9m114 only has a reset pin. The powerdown pin seems to control the regulators suppyling power to the sensor and the privacy LED. Add a mapping of powerdown to powerenable for the mt9m114 for this. While at is also add a comment to document the ov7251 mapping. Signed-off-by: Hans de Goede Reviewed-by: Sakari Ailus Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250507184737.154747-6-hdegoede@redhat.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/intel/int3472/discrete.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index c706671e2f63..4c0aed6e626f 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -142,6 +142,9 @@ struct int3472_gpio_map { }; static const struct int3472_gpio_map int3472_gpio_map[] = { + /* mt9m114 designs declare a powerdown pin which controls the regulators */ + { "INT33F0", INT3472_GPIO_TYPE_POWERDOWN, INT3472_GPIO_TYPE_POWER_ENABLE, false, "vdd" }, + /* ov7251 driver / DT-bindings expect "enable" as con_id for reset */ { "INT347E", INT3472_GPIO_TYPE_RESET, INT3472_GPIO_TYPE_RESET, false, "enable" }, }; -- 2.51.0 From 83579675331059689e2869bf752ca9e17fadbd82 Mon Sep 17 00:00:00 2001 From: Yen-Chi Huang Date: Tue, 6 May 2025 18:23:14 +0800 Subject: [PATCH 07/16] platform/x86: portwell-ec: Add GPIO and WDT driver for Portwell EC MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Adds a driver for the ITE Embedded Controller (EC) on Portwell boards. It integrates with the Linux GPIO and watchdog subsystems to provide: - Control/monitoring of up to 8 EC GPIO pins. - Hardware watchdog timer with 1-255 second timeouts. The driver communicates with the EC via I/O port 0xe300 and identifies the hardware by the "PWG" firmware signature. This enables enhanced system management for Portwell embedded/industrial platforms. Signed-off-by: Yen-Chi Huang Reviewed-by: Linus Walleij Link: https://lore.kernel.org/r/a04be962-b207-4085-af5b-523f59bffcbc@portwell.com.tw Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- MAINTAINERS | 6 + drivers/platform/x86/Kconfig | 15 ++ drivers/platform/x86/Makefile | 3 + drivers/platform/x86/portwell-ec.c | 291 +++++++++++++++++++++++++++++ 4 files changed, 315 insertions(+) create mode 100644 drivers/platform/x86/portwell-ec.c diff --git a/MAINTAINERS b/MAINTAINERS index 08a99fe91bc1..cfb3b51cec83 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -19216,6 +19216,12 @@ S: Maintained F: drivers/pnp/ F: include/linux/pnp.h +PORTWELL EC DRIVER +M: Yen-Chi Huang +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86/portwell-ec.c + POSIX CLOCKS and TIMERS M: Anna-Maria Behnsen M: Frederic Weisbecker diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index f1c82729c4f1..e5cbd58a99f3 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -779,6 +779,21 @@ config PCENGINES_APU2 To compile this driver as a module, choose M here: the module will be called pcengines-apuv2. +config PORTWELL_EC + tristate "Portwell Embedded Controller driver" + depends on X86 && HAS_IOPORT && WATCHDOG && GPIOLIB + select WATCHDOG_CORE + help + This driver provides support for the GPIO pins and watchdog timer + embedded in Portwell's EC. + + Theoretically, this driver should work on multiple Portwell platforms, + but it has only been tested on the Portwell NANO-6064 board. + If you encounter any issues on other boards, please report them. + + To compile this driver as a module, choose M here: the module + will be called portwell-ec. + config BARCO_P50_GPIO tristate "Barco P50 GPIO driver for identify LED/button" depends on GPIOLIB diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index c2a07c7e614c..abbc2644ff6d 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -92,6 +92,9 @@ obj-$(CONFIG_XO1_RFKILL) += xo1-rfkill.o # PC Engines obj-$(CONFIG_PCENGINES_APU2) += pcengines-apuv2.o +# Portwell +obj-$(CONFIG_PORTWELL_EC) += portwell-ec.o + # Barco obj-$(CONFIG_BARCO_P50_GPIO) += barco-p50-gpio.o diff --git a/drivers/platform/x86/portwell-ec.c b/drivers/platform/x86/portwell-ec.c new file mode 100644 index 000000000000..8b788822237b --- /dev/null +++ b/drivers/platform/x86/portwell-ec.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * portwell-ec.c: Portwell embedded controller driver. + * + * Tested on: + * - Portwell NANO-6064 + * + * This driver provides support for GPIO and Watchdog Timer + * functionalities of the Portwell boards with ITE embedded controller (EC). + * The EC is accessed through I/O ports and provides: + * - 8 GPIO pins for control and monitoring + * - Hardware watchdog with 1-15300 second timeout range + * + * It integrates with the Linux GPIO and Watchdog subsystems, allowing + * userspace interaction with EC GPIO pins and watchdog control, + * ensuring system stability and configurability. + * + * (C) Copyright 2025 Portwell, Inc. + * Author: Yen-Chi Huang (jesse.huang@portwell.com.tw) + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PORTWELL_EC_IOSPACE 0xe300 +#define PORTWELL_EC_IOSPACE_LEN SZ_256 + +#define PORTWELL_GPIO_PINS 8 +#define PORTWELL_GPIO_DIR_REG 0x2b +#define PORTWELL_GPIO_VAL_REG 0x2c + +#define PORTWELL_WDT_EC_CONFIG_ADDR 0x06 +#define PORTWELL_WDT_CONFIG_ENABLE 0x1 +#define PORTWELL_WDT_CONFIG_DISABLE 0x0 +#define PORTWELL_WDT_EC_COUNT_MIN_ADDR 0x07 +#define PORTWELL_WDT_EC_COUNT_SEC_ADDR 0x08 +#define PORTWELL_WDT_EC_MAX_COUNT_SECOND (255 * 60) + +#define PORTWELL_EC_FW_VENDOR_ADDRESS 0x4d +#define PORTWELL_EC_FW_VENDOR_LENGTH 3 +#define PORTWELL_EC_FW_VENDOR_NAME "PWG" + +static bool force; +module_param(force, bool, 0444); +MODULE_PARM_DESC(force, "Force loading EC driver without checking DMI boardname"); + +static const struct dmi_system_id pwec_dmi_table[] = { + { + .ident = "NANO-6064 series", + .matches = { + DMI_MATCH(DMI_BOARD_NAME, "NANO-6064"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, pwec_dmi_table); + +/* Functions for access EC via IOSPACE */ + +static void pwec_write(u8 index, u8 data) +{ + outb(data, PORTWELL_EC_IOSPACE + index); +} + +static u8 pwec_read(u8 address) +{ + return inb(PORTWELL_EC_IOSPACE + address); +} + +/* GPIO functions */ + +static int pwec_gpio_get(struct gpio_chip *chip, unsigned int offset) +{ + return pwec_read(PORTWELL_GPIO_VAL_REG) & BIT(offset) ? 1 : 0; +} + +static int pwec_gpio_set_rv(struct gpio_chip *chip, unsigned int offset, int val) +{ + u8 tmp = pwec_read(PORTWELL_GPIO_VAL_REG); + + if (val) + tmp |= BIT(offset); + else + tmp &= ~BIT(offset); + pwec_write(PORTWELL_GPIO_VAL_REG, tmp); + + return 0; +} + +static int pwec_gpio_get_direction(struct gpio_chip *chip, unsigned int offset) +{ + u8 direction = pwec_read(PORTWELL_GPIO_DIR_REG) & BIT(offset); + + if (direction) + return GPIO_LINE_DIRECTION_IN; + + return GPIO_LINE_DIRECTION_OUT; +} + +/* + * Changing direction causes issues on some boards, + * so direction_input and direction_output are disabled for now. + */ + +static int pwec_gpio_direction_input(struct gpio_chip *gc, unsigned int offset) +{ + return -EOPNOTSUPP; +} + +static int pwec_gpio_direction_output(struct gpio_chip *gc, unsigned int offset, int value) +{ + return -EOPNOTSUPP; +} + +static struct gpio_chip pwec_gpio_chip = { + .label = "portwell-ec-gpio", + .get_direction = pwec_gpio_get_direction, + .direction_input = pwec_gpio_direction_input, + .direction_output = pwec_gpio_direction_output, + .get = pwec_gpio_get, + .set_rv = pwec_gpio_set_rv, + .base = -1, + .ngpio = PORTWELL_GPIO_PINS, +}; + +/* Watchdog functions */ + +static void pwec_wdt_write_timeout(unsigned int timeout) +{ + pwec_write(PORTWELL_WDT_EC_COUNT_MIN_ADDR, timeout / 60); + pwec_write(PORTWELL_WDT_EC_COUNT_SEC_ADDR, timeout % 60); +} + +static int pwec_wdt_trigger(struct watchdog_device *wdd) +{ + pwec_wdt_write_timeout(wdd->timeout); + pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_ENABLE); + + return 0; +} + +static int pwec_wdt_start(struct watchdog_device *wdd) +{ + return pwec_wdt_trigger(wdd); +} + +static int pwec_wdt_stop(struct watchdog_device *wdd) +{ + pwec_write(PORTWELL_WDT_EC_CONFIG_ADDR, PORTWELL_WDT_CONFIG_DISABLE); + return 0; +} + +static int pwec_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) +{ + wdd->timeout = timeout; + pwec_wdt_write_timeout(wdd->timeout); + + return 0; +} + +/* Ensure consistent min/sec read in case of second rollover. */ +static unsigned int pwec_wdt_get_timeleft(struct watchdog_device *wdd) +{ + u8 sec, min, old_min; + + do { + old_min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); + sec = pwec_read(PORTWELL_WDT_EC_COUNT_SEC_ADDR); + min = pwec_read(PORTWELL_WDT_EC_COUNT_MIN_ADDR); + } while (min != old_min); + + return min * 60 + sec; +} + +static const struct watchdog_ops pwec_wdt_ops = { + .owner = THIS_MODULE, + .start = pwec_wdt_start, + .stop = pwec_wdt_stop, + .ping = pwec_wdt_trigger, + .set_timeout = pwec_wdt_set_timeout, + .get_timeleft = pwec_wdt_get_timeleft, +}; + +static struct watchdog_device ec_wdt_dev = { + .info = &(struct watchdog_info){ + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "Portwell EC watchdog", + }, + .ops = &pwec_wdt_ops, + .timeout = 60, + .min_timeout = 1, + .max_timeout = PORTWELL_WDT_EC_MAX_COUNT_SECOND, +}; + +static int pwec_firmware_vendor_check(void) +{ + u8 buf[PORTWELL_EC_FW_VENDOR_LENGTH + 1]; + u8 i; + + for (i = 0; i < PORTWELL_EC_FW_VENDOR_LENGTH; i++) + buf[i] = pwec_read(PORTWELL_EC_FW_VENDOR_ADDRESS + i); + buf[PORTWELL_EC_FW_VENDOR_LENGTH] = '\0'; + + return !strcmp(PORTWELL_EC_FW_VENDOR_NAME, buf) ? 0 : -ENODEV; +} + +static int pwec_probe(struct platform_device *pdev) +{ + int ret; + + if (!devm_request_region(&pdev->dev, PORTWELL_EC_IOSPACE, + PORTWELL_EC_IOSPACE_LEN, dev_name(&pdev->dev))) { + dev_err(&pdev->dev, "failed to get IO region\n"); + return -EBUSY; + } + + ret = pwec_firmware_vendor_check(); + if (ret < 0) + return ret; + + ret = devm_gpiochip_add_data(&pdev->dev, &pwec_gpio_chip, NULL); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register Portwell EC GPIO\n"); + return ret; + } + + ret = devm_watchdog_register_device(&pdev->dev, &ec_wdt_dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to register Portwell EC Watchdog\n"); + return ret; + } + + return 0; +} + +static struct platform_driver pwec_driver = { + .driver = { + .name = "portwell-ec", + }, + .probe = pwec_probe, +}; + +static struct platform_device *pwec_dev; + +static int __init pwec_init(void) +{ + int ret; + + if (!dmi_check_system(pwec_dmi_table)) { + if (!force) + return -ENODEV; + pr_warn("force load portwell-ec without DMI check\n"); + } + + ret = platform_driver_register(&pwec_driver); + if (ret) + return ret; + + pwec_dev = platform_device_register_simple("portwell-ec", -1, NULL, 0); + if (IS_ERR(pwec_dev)) { + platform_driver_unregister(&pwec_driver); + return PTR_ERR(pwec_dev); + } + + return 0; +} + +static void __exit pwec_exit(void) +{ + platform_device_unregister(pwec_dev); + platform_driver_unregister(&pwec_driver); +} + +module_init(pwec_init); +module_exit(pwec_exit); + +MODULE_AUTHOR("Yen-Chi Huang "); +MODULE_DESCRIPTION("Portwell EC Driver"); +MODULE_LICENSE("GPL"); -- 2.51.0 From 8e725ff0419ad74dc79abfbdcd4cfba936a5d167 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Fri, 9 May 2025 11:58:01 +0100 Subject: [PATCH 08/16] platform: mellanox: nvsw-sn2200: Fix .items in nvsw_sn2201_busbar_hotplug MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Clang warns (or errors with CONFIG_WERROR=y): drivers/platform/mellanox/nvsw-sn2201.c:531:32: error: variable 'nvsw_sn2201_busbar_items' is not needed and will not be emitted [-Werror,-Wunneeded-internal-declaration] 531 | static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { | ^~~~~~~~~~~~~~~~~~~~~~~~ nvsw_sn2201_busbar_items is only used in ARRAY_SIZE(), which uses sizeof(), so this variable is only used at compile time. It appears that this may be a copy and paste issue, so use nvsw_sn2201_busbar_items as the .items member in nvsw_sn2201_busbar_hotplug, clearing up the warning. Fixes: 56b0bb7f9069 ("platform: mellanox: nvsw-sn2200: Add support for new system flavour") Signed-off-by: Nathan Chancellor Link: https://lore.kernel.org/r/20250509-nvsw-sn2200-fix-items-busbar-hotplug-v1-1-8844fff38dc8@kernel.org Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/mellanox/nvsw-sn2201.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/mellanox/nvsw-sn2201.c b/drivers/platform/mellanox/nvsw-sn2201.c index 8c31189a94dd..db31c8bf2255 100644 --- a/drivers/platform/mellanox/nvsw-sn2201.c +++ b/drivers/platform/mellanox/nvsw-sn2201.c @@ -551,7 +551,7 @@ static struct mlxreg_core_item nvsw_sn2201_busbar_items[] = { static struct mlxreg_core_hotplug_platform_data nvsw_sn2201_busbar_hotplug = { - .items = nvsw_sn2201_items, + .items = nvsw_sn2201_busbar_items, .count = ARRAY_SIZE(nvsw_sn2201_busbar_items), .cell = NVSW_SN2201_SYS_INT_STATUS_OFFSET, .mask = NVSW_SN2201_CPLD_AGGR_BUSBAR_MASK_DEF, -- 2.51.0 From f94ffc3f0b90bf4880e0abf2c056fc465e2c3be8 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 8 May 2025 23:31:39 +0300 Subject: [PATCH 09/16] platform/mellanox: mlxreg-dpu: Fix smatch warnings MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Add missed call to release adapter. Remove wrong error pointer conversion. Fixes: 3e75f2954116 ("platform/mellanox: mlxreg-dpu: Add initial support for Nvidia DPU") Signed-off-by: Vadim Pasternak Link: https://lore.kernel.org/r/20250508203139.55171-1-vadimp@nvidia.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/mellanox/mlxreg-dpu.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/platform/mellanox/mlxreg-dpu.c b/drivers/platform/mellanox/mlxreg-dpu.c index 277e4b8cc5cb..52260106a9f1 100644 --- a/drivers/platform/mellanox/mlxreg-dpu.c +++ b/drivers/platform/mellanox/mlxreg-dpu.c @@ -535,8 +535,10 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) return -EPROBE_DEFER; mlxreg_dpu = devm_kzalloc(&pdev->dev, sizeof(*mlxreg_dpu), GFP_KERNEL); - if (!mlxreg_dpu) - return -ENOMEM; + if (!mlxreg_dpu) { + err = -ENOMEM; + goto alloc_fail; + } /* Create device at the top of DPU I2C tree. */ data->hpdev.client = i2c_new_client_device(data->hpdev.adapter, @@ -562,7 +564,6 @@ static int mlxreg_dpu_probe(struct platform_device *pdev) if (err) { dev_err(&pdev->dev, "Failed to sync regmap for client %s at bus %d at addr 0x%02x\n", data->hpdev.brdinfo->type, data->hpdev.nr, data->hpdev.brdinfo->addr); - err = PTR_ERR(regmap); goto regcache_sync_fail; } @@ -581,6 +582,7 @@ regcache_sync_fail: devm_regmap_init_i2c_fail: i2c_unregister_device(data->hpdev.client); i2c_new_device_fail: +alloc_fail: i2c_put_adapter(data->hpdev.adapter); return err; } -- 2.51.0 From cf8dea42e42b243e41878b57c0ecd898688234e6 Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Tue, 6 May 2025 10:15:40 +0000 Subject: [PATCH 10/16] platform/x86/amd/hsmp: Use a single DRIVER_VERSION for all hsmp modules MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Use a single DRIVER_VERSION for the plat, hsmp and acpi modules, as all these modules are connected to a common functionality. Signed-off-by: Suma Hegde Reviewed-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20250506101542.200811-1-suma.hegde@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- drivers/platform/x86/amd/hsmp/acpi.c | 1 - drivers/platform/x86/amd/hsmp/hsmp.c | 2 -- drivers/platform/x86/amd/hsmp/hsmp.h | 2 ++ drivers/platform/x86/amd/hsmp/plat.c | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index eaae044e4f82..12f4950afcd9 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -28,7 +28,6 @@ #include "hsmp.h" #define DRIVER_NAME "hsmp_acpi" -#define DRIVER_VERSION "2.3" /* These are the strings specified in ACPI table */ #define MSG_IDOFF_STR "MsgIdOffset" diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index a3ac09a90de4..3df34d7436a9 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -32,8 +32,6 @@ #define HSMP_WR true #define HSMP_RD false -#define DRIVER_VERSION "2.4" - /* * When same message numbers are used for both GET and SET operation, * bit:31 indicates whether its SET or GET operation. diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index d58d4f0c20d5..7877cb97993b 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -25,6 +25,8 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" +#define DRIVER_VERSION "2.4" + struct hsmp_mbaddr_info { u32 base_addr; u32 msg_id_off; diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index 81931e808bbc..4f03fdf988c1 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -24,7 +24,6 @@ #include "hsmp.h" #define DRIVER_NAME "amd_hsmp" -#define DRIVER_VERSION "2.3" /* * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox -- 2.51.0 From 92c025db52bb94a032eb3d473bb81e62c19ddbd3 Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Tue, 6 May 2025 10:15:41 +0000 Subject: [PATCH 11/16] platform/x86/amd/hsmp: Report power via hwmon sensors MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Expose power reading and power limits via hwmon power sensors. Signed-off-by: Suma Hegde Reviewed-by: Naveen Krishna Chatradhi Link: https://lore.kernel.org/r/20250506101542.200811-2-suma.hegde@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/arch/x86/amd_hsmp.rst | 8 ++ drivers/platform/x86/amd/hsmp/Makefile | 1 + drivers/platform/x86/amd/hsmp/acpi.c | 4 + drivers/platform/x86/amd/hsmp/hsmp.h | 8 +- drivers/platform/x86/amd/hsmp/hwmon.c | 121 +++++++++++++++++++++++++ drivers/platform/x86/amd/hsmp/plat.c | 5 + 6 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 drivers/platform/x86/amd/hsmp/hwmon.c diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst index 2fd917638e42..3ef3e0a71df9 100644 --- a/Documentation/arch/x86/amd_hsmp.rst +++ b/Documentation/arch/x86/amd_hsmp.rst @@ -116,6 +116,14 @@ for socket with ID00 is given below:: }) } +HSMP HWMON interface +==================== +HSMP power sensors are registered with the hwmon interface. A separate hwmon +directory is created for each socket and the following files are generated +within the hwmon directory. +- power1_input (read only) +- power1_cap_max (read only) +- power1_cap (read, write) An example ========== diff --git a/drivers/platform/x86/amd/hsmp/Makefile b/drivers/platform/x86/amd/hsmp/Makefile index 0759bbcd13f6..ce8342e71f50 100644 --- a/drivers/platform/x86/amd/hsmp/Makefile +++ b/drivers/platform/x86/amd/hsmp/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_AMD_HSMP) += hsmp_common.o hsmp_common-y := hsmp.o +hsmp_common-$(CONFIG_HWMON) += hwmon.o obj-$(CONFIG_AMD_HSMP_PLAT) += amd_hsmp.o amd_hsmp-y := plat.o obj-$(CONFIG_AMD_HSMP_ACPI) += hsmp_acpi.o diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 12f4950afcd9..93b413e0a6e6 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -281,6 +281,10 @@ static int init_acpi(struct device *dev) dev_err(dev, "Failed to init metric table\n"); } + ret = hsmp_create_sensor(dev, sock_ind); + if (ret) + dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + return ret; } diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index 7877cb97993b..d5729f318e52 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -25,7 +26,7 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" -#define DRIVER_VERSION "2.4" +#define DRIVER_VERSION "2.5" struct hsmp_mbaddr_info { u32 base_addr; @@ -63,4 +64,9 @@ int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); +#if IS_REACHABLE(CONFIG_HWMON) +int hsmp_create_sensor(struct device *dev, u16 sock_ind); +#else +static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } +#endif #endif /* HSMP_H */ diff --git a/drivers/platform/x86/amd/hsmp/hwmon.c b/drivers/platform/x86/amd/hsmp/hwmon.c new file mode 100644 index 000000000000..7ffb61e0ef62 --- /dev/null +++ b/drivers/platform/x86/amd/hsmp/hwmon.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD HSMP hwmon support + * Copyright (c) 2025, AMD. + * All Rights Reserved. + * + * This file provides hwmon implementation for HSMP interface. + */ + +#include + +#include +#include +#include +#include +#include + +#include "hsmp.h" + +#define HSMP_HWMON_NAME "amd_hsmp_hwmon" + +static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type, + u32 attr, int channel, long val) +{ + u16 sock_ind = (uintptr_t)dev_get_drvdata(dev); + struct hsmp_message msg = {}; + + if (type != hwmon_power) + return -EOPNOTSUPP; + + if (attr != hwmon_power_cap) + return -EOPNOTSUPP; + + msg.num_args = 1; + msg.args[0] = val / MICROWATT_PER_MILLIWATT; + msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT; + msg.sock_ind = sock_ind; + return hsmp_send_message(&msg); +} + +static int hsmp_hwmon_read(struct device *dev, + enum hwmon_sensor_types type, + u32 attr, int channel, long *val) +{ + u16 sock_ind = (uintptr_t)dev_get_drvdata(dev); + struct hsmp_message msg = {}; + int ret; + + if (type != hwmon_power) + return -EOPNOTSUPP; + + msg.sock_ind = sock_ind; + msg.response_sz = 1; + + switch (attr) { + case hwmon_power_input: + msg.msg_id = HSMP_GET_SOCKET_POWER; + break; + case hwmon_power_cap: + msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT; + break; + case hwmon_power_cap_max: + msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT_MAX; + break; + default: + return -EOPNOTSUPP; + } + + ret = hsmp_send_message(&msg); + if (!ret) + *val = msg.args[0] * MICROWATT_PER_MILLIWATT; + + return ret; +} + +static umode_t hsmp_hwmon_is_visble(const void *data, + enum hwmon_sensor_types type, + u32 attr, int channel) +{ + if (type != hwmon_power) + return 0; + + switch (attr) { + case hwmon_power_input: + return 0444; + case hwmon_power_cap: + return 0644; + case hwmon_power_cap_max: + return 0444; + default: + return 0; + } +} + +static const struct hwmon_ops hsmp_hwmon_ops = { + .read = hsmp_hwmon_read, + .is_visible = hsmp_hwmon_is_visble, + .write = hsmp_hwmon_write, +}; + +static const struct hwmon_channel_info * const hsmp_info[] = { + HWMON_CHANNEL_INFO(power, HWMON_P_INPUT | HWMON_P_CAP | HWMON_P_CAP_MAX), + NULL +}; + +static const struct hwmon_chip_info hsmp_chip_info = { + .ops = &hsmp_hwmon_ops, + .info = hsmp_info, +}; + +int hsmp_create_sensor(struct device *dev, u16 sock_ind) +{ + struct device *hwmon_dev; + + hwmon_dev = devm_hwmon_device_register_with_info(dev, HSMP_HWMON_NAME, + (void *)(uintptr_t)sock_ind, + &hsmp_chip_info, + NULL); + return PTR_ERR_OR_ZERO(hwmon_dev); +} +EXPORT_SYMBOL_NS(hsmp_create_sensor, "AMD_HSMP"); diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c index 4f03fdf988c1..0881d7e01936 100644 --- a/drivers/platform/x86/amd/hsmp/plat.c +++ b/drivers/platform/x86/amd/hsmp/plat.c @@ -189,6 +189,11 @@ static int init_platform_device(struct device *dev) if (ret) dev_err(dev, "Failed to init metric table\n"); } + + /* Register with hwmon interface for reporting power */ + ret = hsmp_create_sensor(dev, i); + if (ret) + dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); } return 0; -- 2.51.0 From 511a4a5ea2b6f1d4e0c719f27db6b627b2b52e49 Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Tue, 6 May 2025 10:15:42 +0000 Subject: [PATCH 12/16] platform/x86/amd/hsmp: acpi: Add sysfs files to display HSMP telemetry MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Make frequently fetched telemetry available via sysfs. These parameters do not fit in hwmon sensor model, hence make them available via sysfs. Create following sysfs files per acpi device node. * c0_residency_input * prochot_status * smu_fw_version * protocol_version * ddr_max_bw(GB/s) * ddr_utilised_bw_input(GB/s) * ddr_utilised_bw_perc_input(%) * mclk_input(MHz) * fclk_input(MHz) * clk_fmax(MHz) * clk_fmin(MHz) * cclk_freq_limit_input(MHz) * pwr_current_active_freq_limit(MHz) * pwr_current_active_freq_limit_source Signed-off-by: Suma Hegde Reviewed-by: Naveen Krishna Chatradhi Reviewed-by: Ilpo Järvinen Link: https://lore.kernel.org/r/20250506101542.200811-3-suma.hegde@amd.com Signed-off-by: Ilpo Järvinen --- Documentation/arch/x86/amd_hsmp.rst | 22 +++ drivers/platform/x86/amd/hsmp/acpi.c | 262 +++++++++++++++++++++++++++ drivers/platform/x86/amd/hsmp/hsmp.c | 23 +++ drivers/platform/x86/amd/hsmp/hsmp.h | 1 + 4 files changed, 308 insertions(+) diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst index 3ef3e0a71df9..a094f55c10b0 100644 --- a/Documentation/arch/x86/amd_hsmp.rst +++ b/Documentation/arch/x86/amd_hsmp.rst @@ -71,6 +71,28 @@ Note: lseek() is not supported as entire metrics table is read. Metrics table definitions will be documented as part of Public PPR. The same is defined in the amd_hsmp.h header. +2. HSMP telemetry sysfs files + +Following sysfs files are available at /sys/devices/platform/AMDI0097:0X/. + +* c0_residency_input: Percentage of cores in C0 state. +* prochot_status: Reports 1 if the processor is at thermal threshold value, + 0 otherwise. +* smu_fw_version: SMU firmware version. +* protocol_version: HSMP interface version. +* ddr_max_bw: Theoretical maximum DDR bandwidth in GB/s. +* ddr_utilised_bw_input: Current utilized DDR bandwidth in GB/s. +* ddr_utilised_bw_perc_input(%): Percentage of current utilized DDR bandwidth. +* mclk_input: Memory clock in MHz. +* fclk_input: Fabric clock in MHz. +* clk_fmax: Maximum frequency of socket in MHz. +* clk_fmin: Minimum frequency of socket in MHz. +* cclk_freq_limit_input: Core clock frequency limit per socket in MHz. +* pwr_current_active_freq_limit: Current active frequency limit of socket + in MHz. +* pwr_current_active_freq_limit_source: Source of current active frequency + limit. + ACPI device object format ========================= The ACPI object format expected from the amd_hsmp driver diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c index 93b413e0a6e6..e9075b129213 100644 --- a/drivers/platform/x86/amd/hsmp/acpi.c +++ b/drivers/platform/x86/amd/hsmp/acpi.c @@ -12,6 +12,9 @@ #include #include +#include +#include +#include #include #include #include @@ -36,6 +39,11 @@ static struct hsmp_plat_device *hsmp_pdev; +struct hsmp_sys_attr { + struct device_attribute dattr; + u32 msg_id; +}; + static int amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset, u32 *value, bool write) { @@ -243,6 +251,215 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, return 0; } +static umode_t hsmp_is_sock_dev_attr_visible(struct kobject *kobj, + struct attribute *attr, int id) +{ + return attr->mode; +} + +#define to_hsmp_sys_attr(_attr) container_of(_attr, struct hsmp_sys_attr, dattr) + +static ssize_t hsmp_msg_resp32_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", data); +} + +#define DDR_MAX_BW_MASK GENMASK(31, 20) +#define DDR_UTIL_BW_MASK GENMASK(19, 8) +#define DDR_UTIL_BW_PERC_MASK GENMASK(7, 0) +#define FW_VER_MAJOR_MASK GENMASK(23, 16) +#define FW_VER_MINOR_MASK GENMASK(15, 8) +#define FW_VER_DEBUG_MASK GENMASK(7, 0) +#define FMAX_MASK GENMASK(31, 16) +#define FMIN_MASK GENMASK(15, 0) +#define FREQ_LIMIT_MASK GENMASK(31, 16) +#define FREQ_SRC_IND_MASK GENMASK(15, 0) + +static ssize_t hsmp_ddr_max_bw_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_MAX_BW_MASK, data)); +} + +static ssize_t hsmp_ddr_util_bw_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_UTIL_BW_MASK, data)); +} + +static ssize_t hsmp_ddr_util_bw_perc_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(DDR_UTIL_BW_PERC_MASK, data)); +} + +static ssize_t hsmp_msg_fw_ver_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu.%lu.%lu\n", + FIELD_GET(FW_VER_MAJOR_MASK, data), + FIELD_GET(FW_VER_MINOR_MASK, data), + FIELD_GET(FW_VER_DEBUG_MASK, data)); +} + +static ssize_t hsmp_fclk_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data[2]; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, data, 2); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", data[0]); +} + +static ssize_t hsmp_mclk_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data[2]; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, data, 2); + if (ret) + return ret; + + return sysfs_emit(buf, "%u\n", data[1]); +} + +static ssize_t hsmp_clk_fmax_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(FMAX_MASK, data)); +} + +static ssize_t hsmp_clk_fmin_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(FMIN_MASK, data)); +} + +static ssize_t hsmp_freq_limit_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + return sysfs_emit(buf, "%lu\n", FIELD_GET(FREQ_LIMIT_MASK, data)); +} + +static const char * const freqlimit_srcnames[] = { + "cHTC-Active", + "PROCHOT", + "TDC limit", + "PPT Limit", + "OPN Max", + "Reliability Limit", + "APML Agent", + "HSMP Agent", +}; + +static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct hsmp_sys_attr *hattr = to_hsmp_sys_attr(attr); + struct hsmp_socket *sock = dev_get_drvdata(dev); + unsigned int index; + int len = 0; + u16 src_ind; + u32 data; + int ret; + + ret = hsmp_msg_get_nargs(sock->sock_ind, hattr->msg_id, &data, 1); + if (ret) + return ret; + + src_ind = FIELD_GET(FREQ_SRC_IND_MASK, data); + for (index = 0; index < ARRAY_SIZE(freqlimit_srcnames); index++) { + if (!src_ind) + break; + if (src_ind & 1) + len += sysfs_emit_at(buf, len, "%s\n", freqlimit_srcnames[index]); + src_ind >>= 1; + } + return len; +} + static int init_acpi(struct device *dev) { u16 sock_ind; @@ -285,6 +502,8 @@ static int init_acpi(struct device *dev) if (ret) dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]); + return ret; } @@ -299,9 +518,52 @@ static const struct bin_attribute *hsmp_attr_list[] = { NULL }; +#define HSMP_DEV_ATTR(_name, _msg_id, _show, _mode) \ +static struct hsmp_sys_attr hattr_##_name = { \ + .dattr = __ATTR(_name, _mode, _show, NULL), \ + .msg_id = _msg_id, \ +} + +HSMP_DEV_ATTR(c0_residency_input, HSMP_GET_C0_PERCENT, hsmp_msg_resp32_show, 0444); +HSMP_DEV_ATTR(prochot_status, HSMP_GET_PROC_HOT, hsmp_msg_resp32_show, 0444); +HSMP_DEV_ATTR(smu_fw_version, HSMP_GET_SMU_VER, hsmp_msg_fw_ver_show, 0444); +HSMP_DEV_ATTR(protocol_version, HSMP_GET_PROTO_VER, hsmp_msg_resp32_show, 0444); +HSMP_DEV_ATTR(cclk_freq_limit_input, HSMP_GET_CCLK_THROTTLE_LIMIT, hsmp_msg_resp32_show, 0444); +HSMP_DEV_ATTR(ddr_max_bw, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_max_bw_show, 0444); +HSMP_DEV_ATTR(ddr_utilised_bw_input, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_util_bw_show, 0444); +HSMP_DEV_ATTR(ddr_utilised_bw_perc_input, HSMP_GET_DDR_BANDWIDTH, hsmp_ddr_util_bw_perc_show, 0444); +HSMP_DEV_ATTR(fclk_input, HSMP_GET_FCLK_MCLK, hsmp_fclk_show, 0444); +HSMP_DEV_ATTR(mclk_input, HSMP_GET_FCLK_MCLK, hsmp_mclk_show, 0444); +HSMP_DEV_ATTR(clk_fmax, HSMP_GET_SOCKET_FMAX_FMIN, hsmp_clk_fmax_show, 0444); +HSMP_DEV_ATTR(clk_fmin, HSMP_GET_SOCKET_FMAX_FMIN, hsmp_clk_fmin_show, 0444); +HSMP_DEV_ATTR(pwr_current_active_freq_limit, HSMP_GET_SOCKET_FREQ_LIMIT, + hsmp_freq_limit_show, 0444); +HSMP_DEV_ATTR(pwr_current_active_freq_limit_source, HSMP_GET_SOCKET_FREQ_LIMIT, + hsmp_freq_limit_source_show, 0444); + +static struct attribute *hsmp_dev_attr_list[] = { + &hattr_c0_residency_input.dattr.attr, + &hattr_prochot_status.dattr.attr, + &hattr_smu_fw_version.dattr.attr, + &hattr_protocol_version.dattr.attr, + &hattr_cclk_freq_limit_input.dattr.attr, + &hattr_ddr_max_bw.dattr.attr, + &hattr_ddr_utilised_bw_input.dattr.attr, + &hattr_ddr_utilised_bw_perc_input.dattr.attr, + &hattr_fclk_input.dattr.attr, + &hattr_mclk_input.dattr.attr, + &hattr_clk_fmax.dattr.attr, + &hattr_clk_fmin.dattr.attr, + &hattr_pwr_current_active_freq_limit.dattr.attr, + &hattr_pwr_current_active_freq_limit_source.dattr.attr, + NULL +}; + static const struct attribute_group hsmp_attr_grp = { .bin_attrs_new = hsmp_attr_list, + .attrs = hsmp_dev_attr_list, .is_bin_visible = hsmp_is_sock_attr_visible, + .is_visible = hsmp_is_sock_dev_attr_visible, }; static const struct attribute_group *hsmp_groups[] = { diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c index 3df34d7436a9..1f0cda87b6e6 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.c +++ b/drivers/platform/x86/amd/hsmp/hsmp.c @@ -228,6 +228,29 @@ int hsmp_send_message(struct hsmp_message *msg) } EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); +int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args) +{ + struct hsmp_message msg = {}; + unsigned int i; + int ret; + + if (!data) + return -EINVAL; + msg.msg_id = msg_id; + msg.sock_ind = sock_ind; + msg.response_sz = num_args; + + ret = hsmp_send_message(&msg); + if (ret) + return ret; + + for (i = 0; i < num_args; i++) + data[i] = msg.args[i]; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(hsmp_msg_get_nargs, "AMD_HSMP"); + int hsmp_test(u16 sock_ind, u32 value) { struct hsmp_message msg = { 0 }; diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h index d5729f318e52..36b5ceea9ac0 100644 --- a/drivers/platform/x86/amd/hsmp/hsmp.h +++ b/drivers/platform/x86/amd/hsmp/hsmp.h @@ -69,4 +69,5 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); #else static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } #endif +int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); #endif /* HSMP_H */ -- 2.51.0 From f4856c20c137a73d73e448caa3964098024248bf Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 29 Apr 2025 02:36:03 +0200 Subject: [PATCH 13/16] power: supply: core: Add additional health status values MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Some batteries can signal when an internal fuse was blown. In such a case POWER_SUPPLY_HEALTH_DEAD is too vague for userspace applications to perform meaningful diagnostics. Additionally some batteries can also signal when some of their internal cells are imbalanced. In such a case returning POWER_SUPPLY_HEALTH_UNSPEC_FAILURE is again too vague for userspace applications to perform meaningful diagnostics. Add new health status values for both cases. Signed-off-by: Armin Wolf Reviewed-by: Sebastian Reichel Link: https://lore.kernel.org/r/20250429003606.303870-1-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/ABI/testing/sysfs-class-power | 2 +- drivers/power/supply/power_supply_sysfs.c | 2 ++ include/linux/power_supply.h | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 78afb2422fc5..dfa824bccf82 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -456,7 +456,7 @@ Description: "Over voltage", "Under voltage", "Unspecified failure", "Cold", "Watchdog timer expire", "Safety timer expire", "Over current", "Calibration required", "Warm", - "Cool", "Hot", "No battery" + "Cool", "Hot", "No battery", "Blown fuse", "Cell imbalance" What: /sys/class/power_supply//precharge_current Date: June 2017 diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index f769d5941d0d..b6918adcabd7 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -110,6 +110,8 @@ static const char * const POWER_SUPPLY_HEALTH_TEXT[] = { [POWER_SUPPLY_HEALTH_COOL] = "Cool", [POWER_SUPPLY_HEALTH_HOT] = "Hot", [POWER_SUPPLY_HEALTH_NO_BATTERY] = "No battery", + [POWER_SUPPLY_HEALTH_BLOWN_FUSE] = "Blown fuse", + [POWER_SUPPLY_HEALTH_CELL_IMBALANCE] = "Cell imbalance", }; static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = { diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index cbec930430a7..03786c8c2efe 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -71,6 +71,8 @@ enum { POWER_SUPPLY_HEALTH_COOL, POWER_SUPPLY_HEALTH_HOT, POWER_SUPPLY_HEALTH_NO_BATTERY, + POWER_SUPPLY_HEALTH_BLOWN_FUSE, + POWER_SUPPLY_HEALTH_CELL_IMBALANCE, }; enum { -- 2.51.0 From 058de163a376b28816f91ff5e2fe6d7bc227e2ae Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 29 Apr 2025 02:36:04 +0200 Subject: [PATCH 14/16] platform/x86: dell-ddv: Implement the battery matching algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Since commit db0a507cb24d ("ACPICA: Update integer-to-hex-string conversions") the battery serial number is no longer distorted, allowing us to finally implement the battery matching algorithm. The battery matchign algorithm is necessary when translating between ACPI batteries and the associated indices used by the WMI interface based on the battery serial number. Since this serial number can only be retrieved when the battery is present we cannot perform the initial translation inside dell_wmi_ddv_add_battery() because the ACPI battery might be absent at this point in time. Introduce dell_wmi_ddv_battery_translate() which implements the battery matching algorithm and replaces dell_wmi_ddv_battery_index(). Also implement a translation cache for caching previous translations between ACPI batteries and indices. This is necessary because performing a translation can be very expensive. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250429003606.303870-2-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/wmi/devices/dell-wmi-ddv.rst | 8 -- drivers/platform/x86/dell/dell-wmi-ddv.c | 101 ++++++++++++++++++--- 2 files changed, 88 insertions(+), 21 deletions(-) diff --git a/Documentation/wmi/devices/dell-wmi-ddv.rst b/Documentation/wmi/devices/dell-wmi-ddv.rst index e0c20af30948..f10a623acca1 100644 --- a/Documentation/wmi/devices/dell-wmi-ddv.rst +++ b/Documentation/wmi/devices/dell-wmi-ddv.rst @@ -260,14 +260,6 @@ Some machines like the Dell Inspiron 3505 only support a single battery and thus ignore the battery index. Because of this the driver depends on the ACPI battery hook mechanism to discover batteries. -.. note:: - The ACPI battery matching algorithm currently used inside the driver is - outdated and does not match the algorithm described above. The reasons for - this are differences in the handling of the ToHexString() ACPI opcode between - Linux and Windows, which distorts the serial number of ACPI batteries on many - machines. Until this issue is resolved, the driver cannot use the above - algorithm. - Reverse-Engineering the DDV WMI interface ========================================= diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index f27739da380f..711639001be4 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -39,6 +39,9 @@ #define DELL_DDV_SUPPORTED_VERSION_MAX 3 #define DELL_DDV_GUID "8A42EA14-4F2A-FD45-6422-0087F7A7E608" +/* Battery indices 1, 2 and 3 */ +#define DELL_DDV_NUM_BATTERIES 3 + #define DELL_EPPID_LENGTH 20 #define DELL_EPPID_EXT_LENGTH 23 @@ -105,6 +108,8 @@ struct dell_wmi_ddv_sensors { struct dell_wmi_ddv_data { struct acpi_battery_hook hook; struct device_attribute eppid_attr; + struct mutex translation_cache_lock; /* Protects the translation cache */ + struct power_supply *translation_cache[DELL_DDV_NUM_BATTERIES]; struct dell_wmi_ddv_sensors fans; struct dell_wmi_ddv_sensors temps; struct wmi_device *wdev; @@ -639,15 +644,78 @@ err_release: return ret; } -static int dell_wmi_ddv_battery_index(struct acpi_device *acpi_dev, u32 *index) +static int dell_wmi_ddv_battery_translate(struct dell_wmi_ddv_data *data, + struct power_supply *battery, u32 *index) { - const char *uid_str; + u32 serial_dec, serial_hex, serial; + union power_supply_propval val; + int ret; + + guard(mutex)(&data->translation_cache_lock); + + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + if (data->translation_cache[i] == battery) { + dev_dbg(&data->wdev->dev, "Translation cache hit for battery index %u\n", + i + 1); + *index = i + 1; + return 0; + } + } + + dev_dbg(&data->wdev->dev, "Translation cache miss\n"); + + /* Perform a translation between a ACPI battery and a battery index */ + + ret = power_supply_get_property(battery, POWER_SUPPLY_PROP_SERIAL_NUMBER, &val); + if (ret < 0) + return ret; + + /* + * Some devices display the serial number of the ACPI battery (string!) as a decimal + * number while other devices display it as a hexadecimal number. Because of this we + * have to check both cases. + */ + ret = kstrtou32(val.strval, 16, &serial_hex); + if (ret < 0) + return ret; /* Should never fail */ + + ret = kstrtou32(val.strval, 10, &serial_dec); + if (ret < 0) + serial_dec = 0; /* Can fail, thus we only mark serial_dec as invalid */ + + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_SERIAL_NUMBER, i + 1, + &serial); + if (ret < 0) + return ret; - uid_str = acpi_device_uid(acpi_dev); - if (!uid_str) - return -ENODEV; + /* A serial number of 0 signals that this index is not associated with a battery */ + if (!serial) + continue; - return kstrtou32(uid_str, 10, index); + if (serial == serial_dec || serial == serial_hex) { + dev_dbg(&data->wdev->dev, "Translation cache update for battery index %u\n", + i + 1); + data->translation_cache[i] = battery; + *index = i + 1; + return 0; + } + } + + return -ENODEV; +} + +static void dell_wmi_battery_invalidate(struct dell_wmi_ddv_data *data, + struct power_supply *battery) +{ + guard(mutex)(&data->translation_cache_lock); + + for (int i = 0; i < ARRAY_SIZE(data->translation_cache); i++) { + if (data->translation_cache[i] == battery) { + data->translation_cache[i] = NULL; + return; + } + } } static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -657,7 +725,7 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha u32 index; int ret; - ret = dell_wmi_ddv_battery_index(to_acpi_device(dev->parent), &index); + ret = dell_wmi_ddv_battery_translate(data, to_power_supply(dev), &index); if (ret < 0) return ret; @@ -684,7 +752,7 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe u32 index, value; int ret; - ret = dell_wmi_ddv_battery_index(to_acpi_device(psy->dev.parent), &index); + ret = dell_wmi_ddv_battery_translate(data, psy, &index); if (ret < 0) return ret; @@ -719,13 +787,12 @@ static const struct power_supply_ext dell_wmi_ddv_extension = { static int dell_wmi_ddv_add_battery(struct power_supply *battery, struct acpi_battery_hook *hook) { struct dell_wmi_ddv_data *data = container_of(hook, struct dell_wmi_ddv_data, hook); - u32 index; int ret; - /* Return 0 instead of error to avoid being unloaded */ - ret = dell_wmi_ddv_battery_index(to_acpi_device(battery->dev.parent), &index); - if (ret < 0) - return 0; + /* + * We cannot do the battery matching here since the battery might be absent, preventing + * us from reading the serial number. + */ ret = device_create_file(&battery->dev, &data->eppid_attr); if (ret < 0) @@ -749,11 +816,19 @@ static int dell_wmi_ddv_remove_battery(struct power_supply *battery, struct acpi device_remove_file(&battery->dev, &data->eppid_attr); power_supply_unregister_extension(battery, &dell_wmi_ddv_extension); + dell_wmi_battery_invalidate(data, battery); + return 0; } static int dell_wmi_ddv_battery_add(struct dell_wmi_ddv_data *data) { + int ret; + + ret = devm_mutex_init(&data->wdev->dev, &data->translation_cache_lock); + if (ret < 0) + return ret; + data->hook.name = "Dell DDV Battery Extension"; data->hook.add_battery = dell_wmi_ddv_add_battery; data->hook.remove_battery = dell_wmi_ddv_remove_battery; -- 2.51.0 From 303ecf690ae2882f1138ea1437207fba5294da34 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 29 Apr 2025 02:36:05 +0200 Subject: [PATCH 15/16] platform/x86: dell-ddv: Expose the battery manufacture date to userspace MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The manufacture date of a given battery is exposed over the Dell DDV WMI interface using the "BatteryManufactureDate" WMI method. The resulting data contains the manufacture date of the battery encoded inside a 16-bit value as described in the Smart Battery Data Specification. Expose this value to userspace using the power supply extension interface. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250429003606.303870-3-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/wmi/devices/dell-wmi-ddv.rst | 3 -- drivers/platform/x86/dell/dell-wmi-ddv.c | 56 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/Documentation/wmi/devices/dell-wmi-ddv.rst b/Documentation/wmi/devices/dell-wmi-ddv.rst index f10a623acca1..41c553d5c77d 100644 --- a/Documentation/wmi/devices/dell-wmi-ddv.rst +++ b/Documentation/wmi/devices/dell-wmi-ddv.rst @@ -118,9 +118,6 @@ The date is encoded in the following manner: - bits 5 to 8 contain the manufacture month. - bits 9 to 15 contain the manufacture year biased by 1980. -.. note:: - The data format needs to be verified on more machines. - WMI method BatterySerialNumber() -------------------------------- diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 711639001be4..0d63934416e9 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -8,6 +8,7 @@ #define pr_format(fmt) KBUILD_MODNAME ": " fmt #include +#include #include #include #include @@ -42,6 +43,10 @@ /* Battery indices 1, 2 and 3 */ #define DELL_DDV_NUM_BATTERIES 3 +#define SBS_MANUFACTURE_YEAR_MASK GENMASK(15, 9) +#define SBS_MANUFACTURE_MONTH_MASK GENMASK(8, 5) +#define SBS_MANUFACTURE_DAY_MASK GENMASK(4, 0) + #define DELL_EPPID_LENGTH 20 #define DELL_EPPID_EXT_LENGTH 23 @@ -744,6 +749,50 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha return ret; } +static int dell_wmi_ddv_get_manufacture_date(struct dell_wmi_ddv_data *data, u32 index, + enum power_supply_property psp, + union power_supply_propval *val) +{ + u16 year, month, day; + u32 value; + int ret; + + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURE_DATE, + index, &value); + if (ret < 0) + return ret; + if (value > U16_MAX) + return -ENXIO; + + /* + * Some devices report a invalid manufacture date value + * like 0.0.1980. Because of this we have to check the + * whole value before exposing parts of it to user space. + */ + year = FIELD_GET(SBS_MANUFACTURE_YEAR_MASK, value) + 1980; + month = FIELD_GET(SBS_MANUFACTURE_MONTH_MASK, value); + if (month < 1 || month > 12) + return -ENODATA; + + day = FIELD_GET(SBS_MANUFACTURE_DAY_MASK, value); + if (day < 1 || day > 31) + return -ENODATA; + + switch (psp) { + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + val->intval = year; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + val->intval = month; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + val->intval = day; + return 0; + default: + return -EINVAL; + } +} + static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct power_supply_ext *ext, void *drvdata, enum power_supply_property psp, union power_supply_propval *val) @@ -768,6 +817,10 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe */ val->intval = value - 2732; return 0; + case POWER_SUPPLY_PROP_MANUFACTURE_YEAR: + case POWER_SUPPLY_PROP_MANUFACTURE_MONTH: + case POWER_SUPPLY_PROP_MANUFACTURE_DAY: + return dell_wmi_ddv_get_manufacture_date(data, index, psp, val); default: return -EINVAL; } @@ -775,6 +828,9 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe static const enum power_supply_property dell_wmi_ddv_properties[] = { POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_MANUFACTURE_YEAR, + POWER_SUPPLY_PROP_MANUFACTURE_MONTH, + POWER_SUPPLY_PROP_MANUFACTURE_DAY, }; static const struct power_supply_ext dell_wmi_ddv_extension = { -- 2.51.0 From 2bd1870a67692bb7bbeba80bb3135934d78eba66 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Tue, 29 Apr 2025 02:36:06 +0200 Subject: [PATCH 16/16] platform/x86: dell-ddv: Expose the battery health to userspace MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The health of a given battery is exposed over the Dell DDV WMI interface using the "BatteryManufactureAceess" WMI method. The resulting data contains, among other data, the health status of the battery. Expose this value to userspace using the power supply extension interface. Tested on a Dell Inspiron 3505. Signed-off-by: Armin Wolf Link: https://lore.kernel.org/r/20250429003606.303870-4-W_Armin@gmx.de Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen --- Documentation/wmi/devices/dell-wmi-ddv.rst | 35 ++++++++- drivers/platform/x86/dell/dell-wmi-ddv.c | 89 ++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) diff --git a/Documentation/wmi/devices/dell-wmi-ddv.rst b/Documentation/wmi/devices/dell-wmi-ddv.rst index 41c553d5c77d..109d4c5c922e 100644 --- a/Documentation/wmi/devices/dell-wmi-ddv.rst +++ b/Documentation/wmi/devices/dell-wmi-ddv.rst @@ -150,7 +150,40 @@ Returns the voltage flow of the battery in mV as an u16. WMI method BatteryManufactureAccess() ------------------------------------- -Returns a manufacture-defined value as an u16. +Returns the health status of the battery as a u16. +The health status encoded in the following manner: + + - the third nibble contains the general failure mode + - the fourth nibble contains the specific failure code + +Valid failure modes are: + + - permanent failure (``0x9``) + - overheat failure (``0xa``) + - overcurrent failure (``0xb``) + +All other failure modes are to be considered normal. + +The following failure codes are valid for a permanent failure: + + - fuse blown (``0x0``) + - cell imbalance (``0x1``) + - overvoltage (``0x2``) + - fet failure (``0x3``) + +The last two bits of the failure code are to be ignored when the battery +signals a permanent failure. + +The following failure codes a valid for a overheat failure: + + - overheat at start of charging (``0x5``) + - overheat during charging (``0x7``) + - overheat during discharging (``0x8``) + +The following failure codes are valid for a overcurrent failure: + + - overcurrent during charging (``0x6``) + - overcurrent during discharging (``0xb``) WMI method BatteryRelativeStateOfCharge() ----------------------------------------- diff --git a/drivers/platform/x86/dell/dell-wmi-ddv.c b/drivers/platform/x86/dell/dell-wmi-ddv.c index 0d63934416e9..67f3d7158403 100644 --- a/drivers/platform/x86/dell/dell-wmi-ddv.c +++ b/drivers/platform/x86/dell/dell-wmi-ddv.c @@ -47,6 +47,26 @@ #define SBS_MANUFACTURE_MONTH_MASK GENMASK(8, 5) #define SBS_MANUFACTURE_DAY_MASK GENMASK(4, 0) +#define MA_FAILURE_MODE_MASK GENMASK(11, 8) +#define MA_FAILURE_MODE_PERMANENT 0x9 +#define MA_FAILURE_MODE_OVERHEAT 0xA +#define MA_FAILURE_MODE_OVERCURRENT 0xB + +#define MA_PERMANENT_FAILURE_CODE_MASK GENMASK(13, 12) +#define MA_PERMANENT_FAILURE_FUSE_BLOWN 0x0 +#define MA_PERMANENT_FAILURE_CELL_IMBALANCE 0x1 +#define MA_PERMANENT_FAILURE_OVERVOLTAGE 0x2 +#define MA_PERMANENT_FAILURE_FET_FAILURE 0x3 + +#define MA_OVERHEAT_FAILURE_CODE_MASK GENMASK(15, 12) +#define MA_OVERHEAT_FAILURE_START 0x5 +#define MA_OVERHEAT_FAILURE_CHARGING 0x7 +#define MA_OVERHEAT_FAILURE_DISCHARGING 0x8 + +#define MA_OVERCURRENT_FAILURE_CODE_MASK GENMASK(15, 12) +#define MA_OVERCURRENT_FAILURE_CHARGING 0x6 +#define MA_OVERCURRENT_FAILURE_DISCHARGING 0xB + #define DELL_EPPID_LENGTH 20 #define DELL_EPPID_EXT_LENGTH 23 @@ -749,6 +769,72 @@ static ssize_t eppid_show(struct device *dev, struct device_attribute *attr, cha return ret; } +static int dell_wmi_ddv_get_health(struct dell_wmi_ddv_data *data, u32 index, + union power_supply_propval *val) +{ + u32 value, code; + int ret; + + ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_MANUFACTURER_ACCESS, index, + &value); + if (ret < 0) + return ret; + + switch (FIELD_GET(MA_FAILURE_MODE_MASK, value)) { + case MA_FAILURE_MODE_PERMANENT: + code = FIELD_GET(MA_PERMANENT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_PERMANENT_FAILURE_FUSE_BLOWN: + val->intval = POWER_SUPPLY_HEALTH_BLOWN_FUSE; + return 0; + case MA_PERMANENT_FAILURE_CELL_IMBALANCE: + val->intval = POWER_SUPPLY_HEALTH_CELL_IMBALANCE; + return 0; + case MA_PERMANENT_FAILURE_OVERVOLTAGE: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + case MA_PERMANENT_FAILURE_FET_FAILURE: + val->intval = POWER_SUPPLY_HEALTH_DEAD; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown permanent failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + case MA_FAILURE_MODE_OVERHEAT: + code = FIELD_GET(MA_OVERHEAT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_OVERHEAT_FAILURE_START: + case MA_OVERHEAT_FAILURE_CHARGING: + case MA_OVERHEAT_FAILURE_DISCHARGING: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown overheat failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + case MA_FAILURE_MODE_OVERCURRENT: + code = FIELD_GET(MA_OVERCURRENT_FAILURE_CODE_MASK, value); + switch (code) { + case MA_OVERCURRENT_FAILURE_CHARGING: + case MA_OVERCURRENT_FAILURE_DISCHARGING: + val->intval = POWER_SUPPLY_HEALTH_OVERCURRENT; + return 0; + default: + dev_notice_once(&data->wdev->dev, "Unknown overcurrent failure code %u\n", + code); + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + default: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + return 0; + } +} + static int dell_wmi_ddv_get_manufacture_date(struct dell_wmi_ddv_data *data, u32 index, enum power_supply_property psp, union power_supply_propval *val) @@ -806,6 +892,8 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe return ret; switch (psp) { + case POWER_SUPPLY_PROP_HEALTH: + return dell_wmi_ddv_get_health(data, index, val); case POWER_SUPPLY_PROP_TEMP: ret = dell_wmi_ddv_query_integer(data->wdev, DELL_DDV_BATTERY_TEMPERATURE, index, &value); @@ -827,6 +915,7 @@ static int dell_wmi_ddv_get_property(struct power_supply *psy, const struct powe } static const enum power_supply_property dell_wmi_ddv_properties[] = { + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_MANUFACTURE_YEAR, POWER_SUPPLY_PROP_MANUFACTURE_MONTH, -- 2.51.0