diff mbox series

[2/3] platform/x86/amd/hsmp: Report power using hwmon sensors

Message ID 20250218123905.51984-3-suma.hegde@amd.com (mailing list archive)
State New
Headers show
Series Add hwmon and sysfs reporting in HSMP | expand

Commit Message

Suma Hegde Feb. 18, 2025, 12:39 p.m. UTC
Expose power reading and power limits via hwmon power sensors.

Signed-off-by: Suma Hegde <suma.hegde@amd.com>
Reviewed-by: Naveen Krishna Chatradhi <naveenkrishna.chatradhi@amd.com>

---
 Documentation/arch/x86/amd_hsmp.rst    |  10 ++
 drivers/platform/x86/amd/hsmp/Makefile |   2 +-
 drivers/platform/x86/amd/hsmp/acpi.c   |   3 +
 drivers/platform/x86/amd/hsmp/hsmp.h   |  12 +-
 drivers/platform/x86/amd/hsmp/hwmon.c  | 191 +++++++++++++++++++++++++
 drivers/platform/x86/amd/hsmp/plat.c   |   3 +
 6 files changed, 219 insertions(+), 2 deletions(-)
 create mode 100644 drivers/platform/x86/amd/hsmp/hwmon.c
diff mbox series

Patch

diff --git a/Documentation/arch/x86/amd_hsmp.rst b/Documentation/arch/x86/amd_hsmp.rst
index 2fd917638e42..9e5fef538f4f 100644
--- a/Documentation/arch/x86/amd_hsmp.rst
+++ b/Documentation/arch/x86/amd_hsmp.rst
@@ -117,6 +117,16 @@  for socket with ID00 is given below::
 		}
 
 
+HSMP HWMON interface
+==================
+HSMP power sensors are registered with hwmon interface.
+Following files with 0444 permission are created.
+- powerx_input
+- powerx_cap_max
+- powerx_cap
+one powerx file is created for each socket. powerx_label is used to
+identify the socket to which this info belongs.
+
 An example
 ==========
 
diff --git a/drivers/platform/x86/amd/hsmp/Makefile b/drivers/platform/x86/amd/hsmp/Makefile
index 3175d8885e87..f63fdc12def1 100644
--- a/drivers/platform/x86/amd/hsmp/Makefile
+++ b/drivers/platform/x86/amd/hsmp/Makefile
@@ -5,7 +5,7 @@ 
 #
 
 obj-$(CONFIG_AMD_HSMP)			+= hsmp_common.o
-hsmp_common-objs			:= hsmp.o
+hsmp_common-objs			:= hsmp.o hwmon.o
 obj-$(CONFIG_AMD_HSMP_PLAT)		+= amd_hsmp.o
 amd_hsmp-objs				:= 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 0c54c91b5f1a..f73b6aedb986 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -342,6 +342,9 @@  static int hsmp_acpi_probe(struct platform_device *pdev)
 		if (ret)
 			return ret;
 		hsmp_pdev->is_probed = true;
+		ret = hsmp_create_sensor(&pdev->dev, hsmp_pdev);
+		if (ret)
+			dev_err(&pdev->dev, "Failed to create hwmon interface.\n");
 	}
 
 	return 0;
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index 3dee0bb684c7..e0227247c995 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -12,6 +12,7 @@ 
 
 #include <linux/compiler_types.h>
 #include <linux/device.h>
+#include <linux/hwmon.h>
 #include <linux/miscdevice.h>
 #include <linux/pci.h>
 #include <linux/semaphore.h>
@@ -26,7 +27,7 @@ 
 #define HSMP_CDEV_NAME		"hsmp_cdev"
 #define HSMP_DEVNODE_NAME	"hsmp"
 
-#define DRIVER_VERSION		"2.4"
+#define DRIVER_VERSION		"2.5"
 
 struct hsmp_mbaddr_info {
 	u32 base_addr;
@@ -49,12 +50,20 @@  struct hsmp_socket {
 	int (*amd_hsmp_rdwr)(struct hsmp_socket *sock, u32 off, u32 *val, bool rw);
 };
 
+struct hsmp_hwmon {
+	char (*p_label)[20];
+	const struct hwmon_channel_info *info[2];
+	struct hwmon_channel_info power_info;
+	struct hwmon_chip_info chip;
+};
+
 struct hsmp_plat_device {
 	struct miscdevice mdev;
 	struct hsmp_socket *sock;
 	u32 proto_ver;
 	u16 num_sockets;
 	bool is_probed;
+	struct hsmp_hwmon hwmon;
 };
 
 int hsmp_cache_proto_ver(u16 sock_ind);
@@ -65,4 +74,5 @@  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);
+int hsmp_create_sensor(struct device *dev, struct hsmp_plat_device *pdev);
 #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..a2925c53fbe9
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hwmon.c
@@ -0,0 +1,191 @@ 
+// 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 <asm/amd_hsmp.h>
+#include <asm-generic/int-ll64.h>
+
+#include <linux/device.h>
+#include <linux/hwmon.h>
+
+#include "hsmp.h"
+
+#define NR_PWR_CHANNELS		3
+#define HSMP_HWMON_NAME		"amd_hsmp_hwmon"
+
+static const char * const power_label[] = {
+	"pinput",
+	"pcap",
+	"pcap_max"
+};
+
+static int hsmp_hwmon_read_label(struct device *dev,
+				 enum hwmon_sensor_types type, u32 attr,
+				 int channel, const char **str)
+{
+	struct hsmp_plat_device *pdev = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_power:
+		*str = pdev->hwmon.p_label[channel];
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+	return 0;
+}
+
+static int hsmp_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+			    u32 attr, int channel, long val)
+{
+	struct hsmp_message msg = { 0 };
+	int ret = -EOPNOTSUPP;
+
+	switch (type) {
+	case hwmon_power:
+		switch (channel % NR_PWR_CHANNELS) {
+		case 1:
+			msg.sock_ind = channel / NR_PWR_CHANNELS;
+			msg.num_args = 1;
+			msg.args[0] = val;
+			msg.msg_id = HSMP_SET_SOCKET_POWER_LIMIT;
+			return hsmp_send_message(&msg);
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static int hsmp_hwmon_read(struct device *dev,
+			   enum hwmon_sensor_types type,
+			   u32 attr, int channel, long *val)
+{
+	struct hsmp_message msg = { 0 };
+	int ret;
+
+	msg.sock_ind = channel / NR_PWR_CHANNELS;
+	msg.response_sz = 1;
+
+	switch (type) {
+	case hwmon_power:
+		switch (channel % NR_PWR_CHANNELS) {
+		case 0:
+			msg.msg_id = HSMP_GET_SOCKET_POWER;
+			break;
+		case 1:
+			msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT;
+			break;
+		case 2:
+			msg.msg_id = HSMP_GET_SOCKET_POWER_LIMIT_MAX;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	ret = hsmp_send_message(&msg);
+	if (!ret)
+		*val = msg.args[0];
+
+	return ret;
+}
+
+static umode_t hsmp_hwmon_is_visble(const void *data,
+				    enum hwmon_sensor_types type,
+				    u32 attr, int channel)
+{
+	switch (type) {
+	case hwmon_power:
+		switch (channel % NR_PWR_CHANNELS) {
+		case 0:
+		case 2:
+			return 0444;
+		case 1:
+			return 0644;
+		default:
+			break;
+		}
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+const struct hwmon_ops hsmp_hwmon_ops = {
+	.read = hsmp_hwmon_read,
+	.read_string = hsmp_hwmon_read_label,
+	.is_visible = hsmp_hwmon_is_visble,
+	.write	= hsmp_hwmon_write,
+};
+
+static int hsmp_create_power_sensor(struct device *dev, struct hsmp_plat_device *pdev)
+{
+	char (*p_label)[20];
+	u32 *p_config;
+	int j;
+
+	p_config = devm_kcalloc(dev, pdev->num_sockets * NR_PWR_CHANNELS + 1,
+				sizeof(*p_config), GFP_KERNEL);
+	if (!p_config)
+		return -ENOMEM;
+
+	p_label = devm_kcalloc(dev, NR_PWR_CHANNELS * pdev->num_sockets,
+			       sizeof(*p_label), GFP_KERNEL);
+	if (!p_label)
+		return -ENOMEM;
+
+	for (j = 0; j < pdev->num_sockets * NR_PWR_CHANNELS;) {
+		p_config[j] = HWMON_P_INPUT | HWMON_P_LABEL;
+		p_config[j + 1] = HWMON_P_CAP | HWMON_P_LABEL;
+		p_config[j + 2] = HWMON_P_CAP_MAX | HWMON_P_LABEL;
+		scnprintf(p_label[j], 20, "sock%u_%s", j / NR_PWR_CHANNELS, power_label[0]);
+		scnprintf(p_label[j + 1], 20, "sock%u_%s", j / NR_PWR_CHANNELS, power_label[1]);
+		scnprintf(p_label[j + 2], 20, "sock%u_%s", j / NR_PWR_CHANNELS, power_label[2]);
+		j += NR_PWR_CHANNELS;
+	}
+
+	pdev->hwmon.power_info.type = hwmon_power;
+	pdev->hwmon.power_info.config = p_config;
+	pdev->hwmon.info[0] = &pdev->hwmon.power_info;
+	pdev->hwmon.p_label = p_label;
+
+	return 0;
+}
+
+int hsmp_create_sensor(struct device *dev, struct hsmp_plat_device *pdev)
+{
+	struct device *hwmon_dev;
+	int ret;
+
+	ret = hsmp_create_power_sensor(dev, pdev);
+	if (ret)
+		return ret;
+
+	pdev->hwmon.chip.ops	= &hsmp_hwmon_ops;
+	pdev->hwmon.chip.info	= pdev->hwmon.info;
+
+	hwmon_dev = devm_hwmon_device_register_with_info(dev, HSMP_HWMON_NAME,
+							 pdev,
+							 &pdev->hwmon.chip,
+							 NULL);
+	if (IS_ERR(hwmon_dev))
+		return PTR_ERR(hwmon_dev);
+
+	return 0;
+}
+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 63034408985c..2bb590a34642 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -220,6 +220,9 @@  static int hsmp_pltdrv_probe(struct platform_device *pdev)
 		dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
 		return ret;
 	}
+	ret = hsmp_create_sensor(&pdev->dev, hsmp_pdev);
+	if (ret)
+		dev_err(&pdev->dev, "Failed to create hwmon interface.\n");
 
 	return hsmp_misc_register(&pdev->dev);
 }