summaryrefslogtreecommitdiff
path: root/drivers/platform/x86/amd
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/platform/x86/amd')
-rw-r--r--drivers/platform/x86/amd/Kconfig29
-rw-r--r--drivers/platform/x86/amd/Makefile6
-rw-r--r--drivers/platform/x86/amd/amd_isp4.c311
-rw-r--r--drivers/platform/x86/amd/hsmp.c952
-rw-r--r--drivers/platform/x86/amd/hsmp/Kconfig49
-rw-r--r--drivers/platform/x86/amd/hsmp/Makefile13
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c643
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c463
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h73
-rw-r--r--drivers/platform/x86/amd/hsmp/hwmon.c121
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c334
-rw-r--r--drivers/platform/x86/amd/pmc/Kconfig17
-rw-r--r--drivers/platform/x86/amd/pmc/Makefile5
-rw-r--r--drivers/platform/x86/amd/pmc/mp1_stb.c332
-rw-r--r--drivers/platform/x86/amd/pmc/mp2_stb.c280
-rw-r--r--drivers/platform/x86/amd/pmc/pmc-quirks.c10
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c523
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.h122
-rw-r--r--drivers/platform/x86/amd/pmf/Kconfig3
-rw-r--r--drivers/platform/x86/amd/pmf/Makefile8
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c103
-rw-r--r--drivers/platform/x86/amd/pmf/auto-mode.c4
-rw-r--r--drivers/platform/x86/amd/pmf/cnqf.c8
-rw-r--r--drivers/platform/x86/amd/pmf/core.c71
-rw-r--r--drivers/platform/x86/amd/pmf/pmf-quirks.c51
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h126
-rw-r--r--drivers/platform/x86/amd/pmf/spc.c159
-rw-r--r--drivers/platform/x86/amd/pmf/sps.c72
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c230
-rw-r--r--drivers/platform/x86/amd/x3d_vcache.c176
30 files changed, 3675 insertions, 1619 deletions
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index f88682d36447..63e4bd985699 100644
--- a/drivers/platform/x86/amd/Kconfig
+++ b/drivers/platform/x86/amd/Kconfig
@@ -3,21 +3,21 @@
# AMD x86 Platform Specific Drivers
#
+source "drivers/platform/x86/amd/hsmp/Kconfig"
source "drivers/platform/x86/amd/pmf/Kconfig"
source "drivers/platform/x86/amd/pmc/Kconfig"
-config AMD_HSMP
- tristate "AMD HSMP Driver"
- depends on AMD_NB && X86_64 && ACPI
+config AMD_3D_VCACHE
+ tristate "AMD 3D V-Cache Performance Optimizer Driver"
+ depends on X86_64 && ACPI
help
- The driver provides a way for user space tools to monitor and manage
- system management functionality on EPYC server CPUs from AMD.
-
- Host System Management Port (HSMP) interface is a mailbox interface
- between the x86 core and the System Management Unit (SMU) firmware.
+ The driver provides a sysfs interface, enabling the setting of a bias
+ that alters CPU core reordering. This bias prefers cores with higher
+ frequencies or larger L3 caches on processors supporting AMD 3D V-Cache
+ technology.
If you choose to compile this driver as a module the module will be
- called amd_hsmp.
+ called amd_3d_vcache.
config AMD_WBRF
bool "AMD Wifi RF Band mitigations (WBRF)"
@@ -32,3 +32,14 @@ config AMD_WBRF
This mechanism will only be activated on platforms that advertise a
need for it.
+
+config AMD_ISP_PLATFORM
+ tristate "AMD ISP4 platform driver"
+ depends on I2C && X86_64 && ACPI
+ help
+ Platform driver for AMD platforms containing image signal processor
+ gen 4. Provides camera sensor module board information to allow
+ sensor and V4L drivers to work properly.
+
+ This driver can also be built as a module. If so, the module
+ will be called amd_isp4.
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index dcec0a46f8af..b0e284b5d497 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -4,8 +4,10 @@
# AMD x86 Platform-Specific Drivers
#
+obj-$(CONFIG_AMD_3D_VCACHE) += amd_3d_vcache.o
+amd_3d_vcache-y := x3d_vcache.o
obj-$(CONFIG_AMD_PMC) += pmc/
-amd_hsmp-y := hsmp.o
-obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o
+obj-$(CONFIG_AMD_HSMP) += hsmp/
obj-$(CONFIG_AMD_PMF) += pmf/
obj-$(CONFIG_AMD_WBRF) += wbrf.o
+obj-$(CONFIG_AMD_ISP_PLATFORM) += amd_isp4.o
diff --git a/drivers/platform/x86/amd/amd_isp4.c b/drivers/platform/x86/amd/amd_isp4.c
new file mode 100644
index 000000000000..0cc01441bcbb
--- /dev/null
+++ b/drivers/platform/x86/amd/amd_isp4.c
@@ -0,0 +1,311 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * AMD ISP platform driver for sensor i2-client instantiation
+ *
+ * Copyright 2025 Advanced Micro Devices, Inc.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define AMDISP_OV05C10_I2C_ADDR 0x10
+#define AMDISP_OV05C10_HID "OMNI5C10"
+#define AMDISP_OV05C10_REMOTE_EP_NAME "ov05c10_isp_4_1_1"
+#define AMD_ISP_PLAT_DRV_NAME "amd-isp4"
+
+/*
+ * AMD ISP platform info definition to initialize sensor
+ * specific platform configuration to prepare the amdisp
+ * platform.
+ */
+struct amdisp_platform_info {
+ struct i2c_board_info board_info;
+ const struct software_node **swnodes;
+};
+
+/*
+ * AMD ISP platform definition to configure the device properties
+ * missing in the ACPI table.
+ */
+struct amdisp_platform {
+ const struct amdisp_platform_info *pinfo;
+ struct i2c_board_info board_info;
+ struct notifier_block i2c_nb;
+ struct i2c_client *i2c_dev;
+ struct mutex lock; /* protects i2c client creation */
+};
+
+/* Top-level OV05C10 camera node property table */
+static const struct property_entry ov05c10_camera_props[] = {
+ PROPERTY_ENTRY_U32("clock-frequency", 24 * HZ_PER_MHZ),
+ { }
+};
+
+/* Root AMD ISP OV05C10 camera node definition */
+static const struct software_node camera_node = {
+ .name = AMDISP_OV05C10_HID,
+ .properties = ov05c10_camera_props,
+};
+
+/*
+ * AMD ISP OV05C10 Ports node definition. No properties defined for
+ * ports node for OV05C10.
+ */
+static const struct software_node ports = {
+ .name = "ports",
+ .parent = &camera_node,
+};
+
+/*
+ * AMD ISP OV05C10 Port node definition. No properties defined for
+ * port node for OV05C10.
+ */
+static const struct software_node port_node = {
+ .name = "port@",
+ .parent = &ports,
+};
+
+/*
+ * Remote endpoint AMD ISP node definition. No properties defined for
+ * remote endpoint node for OV05C10.
+ */
+static const struct software_node remote_ep_isp_node = {
+ .name = AMDISP_OV05C10_REMOTE_EP_NAME,
+};
+
+/*
+ * Remote endpoint reference for isp node included in the
+ * OV05C10 endpoint.
+ */
+static const struct software_node_ref_args ov05c10_refs[] = {
+ SOFTWARE_NODE_REFERENCE(&remote_ep_isp_node),
+};
+
+/* OV05C10 supports one single link frequency */
+static const u64 ov05c10_link_freqs[] = {
+ 925 * HZ_PER_MHZ,
+};
+
+/* OV05C10 supports only 2-lane configuration */
+static const u32 ov05c10_data_lanes[] = {
+ 1,
+ 2,
+};
+
+/* OV05C10 endpoint node properties table */
+static const struct property_entry ov05c10_endpoint_props[] = {
+ PROPERTY_ENTRY_U32("bus-type", 4),
+ PROPERTY_ENTRY_U32_ARRAY_LEN("data-lanes", ov05c10_data_lanes,
+ ARRAY_SIZE(ov05c10_data_lanes)),
+ PROPERTY_ENTRY_U64_ARRAY_LEN("link-frequencies", ov05c10_link_freqs,
+ ARRAY_SIZE(ov05c10_link_freqs)),
+ PROPERTY_ENTRY_REF_ARRAY("remote-endpoint", ov05c10_refs),
+ { }
+};
+
+/* AMD ISP endpoint node definition */
+static const struct software_node endpoint_node = {
+ .name = "endpoint",
+ .parent = &port_node,
+ .properties = ov05c10_endpoint_props,
+};
+
+/*
+ * AMD ISP swnode graph uses 5 nodes and also its relationship is
+ * fixed to align with the structure that v4l2 expects for successful
+ * endpoint fwnode parsing.
+ *
+ * It is only the node property_entries that will vary for each platform
+ * supporting different sensor modules.
+ */
+static const struct software_node *ov05c10_nodes[] = {
+ &camera_node,
+ &ports,
+ &port_node,
+ &endpoint_node,
+ &remote_ep_isp_node,
+ NULL
+};
+
+/* OV05C10 specific AMD ISP platform configuration */
+static const struct amdisp_platform_info ov05c10_platform_config = {
+ .board_info = {
+ .dev_name = "ov05c10",
+ I2C_BOARD_INFO("ov05c10", AMDISP_OV05C10_I2C_ADDR),
+ },
+ .swnodes = ov05c10_nodes,
+};
+
+static const struct acpi_device_id amdisp_sensor_ids[] = {
+ { AMDISP_OV05C10_HID, (kernel_ulong_t)&ov05c10_platform_config },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, amdisp_sensor_ids);
+
+static inline bool is_isp_i2c_adapter(struct i2c_adapter *adap)
+{
+ return !strcmp(adap->owner->name, "i2c_designware_amdisp");
+}
+
+static void instantiate_isp_i2c_client(struct amdisp_platform *isp4_platform,
+ struct i2c_adapter *adap)
+{
+ struct i2c_board_info *info = &isp4_platform->board_info;
+ struct i2c_client *i2c_dev;
+
+ guard(mutex)(&isp4_platform->lock);
+
+ if (isp4_platform->i2c_dev)
+ return;
+
+ i2c_dev = i2c_new_client_device(adap, info);
+ if (IS_ERR(i2c_dev)) {
+ dev_err(&adap->dev, "error %pe registering isp i2c_client\n", i2c_dev);
+ return;
+ }
+ isp4_platform->i2c_dev = i2c_dev;
+}
+
+static int isp_i2c_bus_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct amdisp_platform *isp4_platform =
+ container_of(nb, struct amdisp_platform, i2c_nb);
+ struct device *dev = data;
+ struct i2c_client *client;
+ struct i2c_adapter *adap;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ adap = i2c_verify_adapter(dev);
+ if (!adap)
+ break;
+ if (is_isp_i2c_adapter(adap))
+ instantiate_isp_i2c_client(isp4_platform, adap);
+ break;
+ case BUS_NOTIFY_REMOVED_DEVICE:
+ client = i2c_verify_client(dev);
+ if (!client)
+ break;
+
+ scoped_guard(mutex, &isp4_platform->lock) {
+ if (isp4_platform->i2c_dev == client) {
+ dev_dbg(&client->adapter->dev, "amdisp i2c_client removed\n");
+ isp4_platform->i2c_dev = NULL;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct amdisp_platform *prepare_amdisp_platform(struct device *dev,
+ const struct amdisp_platform_info *src)
+{
+ struct amdisp_platform *isp4_platform;
+ int ret;
+
+ isp4_platform = devm_kzalloc(dev, sizeof(*isp4_platform), GFP_KERNEL);
+ if (!isp4_platform)
+ return ERR_PTR(-ENOMEM);
+
+ ret = devm_mutex_init(dev, &isp4_platform->lock);
+ if (ret)
+ return ERR_PTR(ret);
+
+ isp4_platform->board_info.dev_name = src->board_info.dev_name;
+ strscpy(isp4_platform->board_info.type, src->board_info.type);
+ isp4_platform->board_info.addr = src->board_info.addr;
+ isp4_platform->pinfo = src;
+
+ ret = software_node_register_node_group(src->swnodes);
+ if (ret)
+ return ERR_PTR(ret);
+
+ isp4_platform->board_info.swnode = src->swnodes[0];
+
+ return isp4_platform;
+}
+
+static int try_to_instantiate_i2c_client(struct device *dev, void *data)
+{
+ struct i2c_adapter *adap = i2c_verify_adapter(dev);
+ struct amdisp_platform *isp4_platform = data;
+
+ if (!isp4_platform || !adap)
+ return 0;
+ if (!adap->owner)
+ return 0;
+
+ if (is_isp_i2c_adapter(adap))
+ instantiate_isp_i2c_client(isp4_platform, adap);
+
+ return 0;
+}
+
+static int amd_isp_probe(struct platform_device *pdev)
+{
+ const struct amdisp_platform_info *pinfo;
+ struct amdisp_platform *isp4_platform;
+ int ret;
+
+ pinfo = device_get_match_data(&pdev->dev);
+ if (!pinfo)
+ return dev_err_probe(&pdev->dev, -EINVAL,
+ "failed to get valid ACPI data\n");
+
+ isp4_platform = prepare_amdisp_platform(&pdev->dev, pinfo);
+ if (IS_ERR(isp4_platform))
+ return dev_err_probe(&pdev->dev, PTR_ERR(isp4_platform),
+ "failed to prepare AMD ISP platform fwnode\n");
+
+ isp4_platform->i2c_nb.notifier_call = isp_i2c_bus_notify;
+ ret = bus_register_notifier(&i2c_bus_type, &isp4_platform->i2c_nb);
+ if (ret)
+ goto error_unregister_sw_node;
+
+ /* check if adapter is already registered and create i2c client instance */
+ i2c_for_each_dev(isp4_platform, try_to_instantiate_i2c_client);
+
+ platform_set_drvdata(pdev, isp4_platform);
+ return 0;
+
+error_unregister_sw_node:
+ software_node_unregister_node_group(isp4_platform->pinfo->swnodes);
+ return ret;
+}
+
+static void amd_isp_remove(struct platform_device *pdev)
+{
+ struct amdisp_platform *isp4_platform = platform_get_drvdata(pdev);
+
+ bus_unregister_notifier(&i2c_bus_type, &isp4_platform->i2c_nb);
+ i2c_unregister_device(isp4_platform->i2c_dev);
+ software_node_unregister_node_group(isp4_platform->pinfo->swnodes);
+}
+
+static struct platform_driver amd_isp_platform_driver = {
+ .driver = {
+ .name = AMD_ISP_PLAT_DRV_NAME,
+ .acpi_match_table = amdisp_sensor_ids,
+ },
+ .probe = amd_isp_probe,
+ .remove = amd_isp_remove,
+};
+
+module_platform_driver(amd_isp_platform_driver);
+
+MODULE_AUTHOR("Benjamin Chan <benjamin.chan@amd.com>");
+MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
+MODULE_DESCRIPTION("AMD ISP4 Platform Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/hsmp.c b/drivers/platform/x86/amd/hsmp.c
deleted file mode 100644
index 1927be901108..000000000000
--- a/drivers/platform/x86/amd/hsmp.c
+++ /dev/null
@@ -1,952 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * AMD HSMP Platform Driver
- * Copyright (c) 2022, AMD.
- * All Rights Reserved.
- *
- * This file provides a device implementation for HSMP interface
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <asm/amd_hsmp.h>
-#include <asm/amd_nb.h>
-#include <linux/delay.h>
-#include <linux/io.h>
-#include <linux/miscdevice.h>
-#include <linux/module.h>
-#include <linux/pci.h>
-#include <linux/platform_device.h>
-#include <linux/semaphore.h>
-#include <linux/acpi.h>
-
-#define DRIVER_NAME "amd_hsmp"
-#define DRIVER_VERSION "2.2"
-#define ACPI_HSMP_DEVICE_HID "AMDI0097"
-
-/* HSMP Status / Error codes */
-#define HSMP_STATUS_NOT_READY 0x00
-#define HSMP_STATUS_OK 0x01
-#define HSMP_ERR_INVALID_MSG 0xFE
-#define HSMP_ERR_INVALID_INPUT 0xFF
-
-/* Timeout in millsec */
-#define HSMP_MSG_TIMEOUT 100
-#define HSMP_SHORT_SLEEP 1
-
-#define HSMP_WR true
-#define HSMP_RD false
-
-/*
- * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
- * register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
- * Below are required SMN address for HSMP Mailbox register offsets in SMU address space
- */
-#define SMN_HSMP_BASE 0x3B00000
-#define SMN_HSMP_MSG_ID 0x0010534
-#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
-#define SMN_HSMP_MSG_RESP 0x0010980
-#define SMN_HSMP_MSG_DATA 0x00109E0
-
-#define HSMP_INDEX_REG 0xc4
-#define HSMP_DATA_REG 0xc8
-
-#define HSMP_CDEV_NAME "hsmp_cdev"
-#define HSMP_DEVNODE_NAME "hsmp"
-#define HSMP_METRICS_TABLE_NAME "metrics_bin"
-
-#define HSMP_ATTR_GRP_NAME_SIZE 10
-
-/* These are the strings specified in ACPI table */
-#define MSG_IDOFF_STR "MsgIdOffset"
-#define MSG_ARGOFF_STR "MsgArgOffset"
-#define MSG_RESPOFF_STR "MsgRspOffset"
-
-#define MAX_AMD_SOCKETS 8
-
-struct hsmp_mbaddr_info {
- u32 base_addr;
- u32 msg_id_off;
- u32 msg_resp_off;
- u32 msg_arg_off;
- u32 size;
-};
-
-struct hsmp_socket {
- struct bin_attribute hsmp_attr;
- struct hsmp_mbaddr_info mbinfo;
- void __iomem *metric_tbl_addr;
- void __iomem *virt_base_addr;
- struct semaphore hsmp_sem;
- char name[HSMP_ATTR_GRP_NAME_SIZE];
- struct pci_dev *root;
- struct device *dev;
- u16 sock_ind;
-};
-
-struct hsmp_plat_device {
- struct miscdevice hsmp_device;
- struct hsmp_socket *sock;
- u32 proto_ver;
- u16 num_sockets;
- bool is_acpi_device;
- bool is_probed;
-};
-
-static struct hsmp_plat_device plat_dev;
-
-static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
- u32 *value, bool write)
-{
- int ret;
-
- if (!sock->root)
- return -ENODEV;
-
- ret = pci_write_config_dword(sock->root, HSMP_INDEX_REG,
- sock->mbinfo.base_addr + offset);
- if (ret)
- return ret;
-
- ret = (write ? pci_write_config_dword(sock->root, HSMP_DATA_REG, *value)
- : pci_read_config_dword(sock->root, HSMP_DATA_REG, value));
-
- return ret;
-}
-
-static void amd_hsmp_acpi_rdwr(struct hsmp_socket *sock, u32 offset,
- u32 *value, bool write)
-{
- if (write)
- iowrite32(*value, sock->virt_base_addr + offset);
- else
- *value = ioread32(sock->virt_base_addr + offset);
-}
-
-static int amd_hsmp_rdwr(struct hsmp_socket *sock, u32 offset,
- u32 *value, bool write)
-{
- if (plat_dev.is_acpi_device)
- amd_hsmp_acpi_rdwr(sock, offset, value, write);
- else
- return amd_hsmp_pci_rdwr(sock, offset, value, write);
-
- return 0;
-}
-
-/*
- * Send a message to the HSMP port via PCI-e config space registers
- * or by writing to MMIO space.
- *
- * The caller is expected to zero out any unused arguments.
- * If a response is expected, the number of response words should be greater than 0.
- *
- * Returns 0 for success and populates the requested number of arguments.
- * Returns a negative error code for failure.
- */
-static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
-{
- struct hsmp_mbaddr_info *mbinfo;
- unsigned long timeout, short_sleep;
- u32 mbox_status;
- u32 index;
- int ret;
-
- mbinfo = &sock->mbinfo;
-
- /* Clear the status register */
- mbox_status = HSMP_STATUS_NOT_READY;
- ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
- if (ret) {
- pr_err("Error %d clearing mailbox status register\n", ret);
- return ret;
- }
-
- index = 0;
- /* Write any message arguments */
- while (index < msg->num_args) {
- ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
- &msg->args[index], HSMP_WR);
- if (ret) {
- pr_err("Error %d writing message argument %d\n", ret, index);
- return ret;
- }
- index++;
- }
-
- /* Write the message ID which starts the operation */
- ret = amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
- if (ret) {
- pr_err("Error %d writing message ID %u\n", ret, msg->msg_id);
- return ret;
- }
-
- /*
- * Depending on when the trigger write completes relative to the SMU
- * firmware 1 ms cycle, the operation may take from tens of us to 1 ms
- * to complete. Some operations may take more. Therefore we will try
- * a few short duration sleeps and switch to long sleeps if we don't
- * succeed quickly.
- */
- short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
- timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
-
- while (time_before(jiffies, timeout)) {
- ret = amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
- if (ret) {
- pr_err("Error %d reading mailbox status\n", ret);
- return ret;
- }
-
- if (mbox_status != HSMP_STATUS_NOT_READY)
- break;
- if (time_before(jiffies, short_sleep))
- usleep_range(50, 100);
- else
- usleep_range(1000, 2000);
- }
-
- if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
- return -ETIMEDOUT;
- } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
- return -ENOMSG;
- } else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
- return -EINVAL;
- } else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
- pr_err("Message ID %u unknown failure (status = 0x%X)\n",
- msg->msg_id, mbox_status);
- return -EIO;
- }
-
- /*
- * SMU has responded OK. Read response data.
- * SMU reads the input arguments from eight 32 bit registers starting
- * from SMN_HSMP_MSG_DATA and writes the response data to the same
- * SMN_HSMP_MSG_DATA address.
- * We copy the response data if any, back to the args[].
- */
- index = 0;
- while (index < msg->response_sz) {
- ret = amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
- &msg->args[index], HSMP_RD);
- if (ret) {
- pr_err("Error %d reading response %u for message ID:%u\n",
- ret, index, msg->msg_id);
- break;
- }
- index++;
- }
-
- return ret;
-}
-
-static int validate_message(struct hsmp_message *msg)
-{
- /* msg_id against valid range of message IDs */
- if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
- return -ENOMSG;
-
- /* msg_id is a reserved message ID */
- if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
- return -ENOMSG;
-
- /* num_args and response_sz against the HSMP spec */
- if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args ||
- msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
- return -EINVAL;
-
- return 0;
-}
-
-int hsmp_send_message(struct hsmp_message *msg)
-{
- struct hsmp_socket *sock;
- int ret;
-
- if (!msg)
- return -EINVAL;
- ret = validate_message(msg);
- if (ret)
- return ret;
-
- if (!plat_dev.sock || msg->sock_ind >= plat_dev.num_sockets)
- return -ENODEV;
- sock = &plat_dev.sock[msg->sock_ind];
-
- /*
- * The time taken by smu operation to complete is between
- * 10us to 1ms. Sometime it may take more time.
- * In SMP system timeout of 100 millisecs should
- * be enough for the previous thread to finish the operation
- */
- ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
- if (ret < 0)
- return ret;
-
- ret = __hsmp_send_message(sock, msg);
-
- up(&sock->hsmp_sem);
-
- return ret;
-}
-EXPORT_SYMBOL_GPL(hsmp_send_message);
-
-static int hsmp_test(u16 sock_ind, u32 value)
-{
- struct hsmp_message msg = { 0 };
- int ret;
-
- /*
- * Test the hsmp port by performing TEST command. The test message
- * takes one argument and returns the value of that argument + 1.
- */
- msg.msg_id = HSMP_TEST;
- msg.num_args = 1;
- msg.response_sz = 1;
- msg.args[0] = value;
- msg.sock_ind = sock_ind;
-
- ret = hsmp_send_message(&msg);
- if (ret)
- return ret;
-
- /* Check the response value */
- if (msg.args[0] != (value + 1)) {
- dev_err(plat_dev.sock[sock_ind].dev,
- "Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
- sock_ind, (value + 1), msg.args[0]);
- return -EBADE;
- }
-
- return ret;
-}
-
-static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
-{
- int __user *arguser = (int __user *)arg;
- struct hsmp_message msg = { 0 };
- int ret;
-
- if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
- return -EFAULT;
-
- /*
- * Check msg_id is within the range of supported msg ids
- * i.e within the array bounds of hsmp_msg_desc_table
- */
- if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
- return -ENOMSG;
-
- switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
- case FMODE_WRITE:
- /*
- * Device is opened in O_WRONLY mode
- * Execute only set/configure commands
- */
- if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET)
- return -EINVAL;
- break;
- case FMODE_READ:
- /*
- * Device is opened in O_RDONLY mode
- * Execute only get/monitor commands
- */
- if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET)
- return -EINVAL;
- break;
- case FMODE_READ | FMODE_WRITE:
- /*
- * Device is opened in O_RDWR mode
- * Execute both get/monitor and set/configure commands
- */
- break;
- default:
- return -EINVAL;
- }
-
- ret = hsmp_send_message(&msg);
- if (ret)
- return ret;
-
- if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
- /* Copy results back to user for get/monitor commands */
- if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
- return -EFAULT;
- }
-
- return 0;
-}
-
-static const struct file_operations hsmp_fops = {
- .owner = THIS_MODULE,
- .unlocked_ioctl = hsmp_ioctl,
- .compat_ioctl = hsmp_ioctl,
-};
-
-/* This is the UUID used for HSMP */
-static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
- 0xa6, 0x9f, 0x4e, 0xa2,
- 0x87, 0x1f, 0xc2, 0xf6);
-
-static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
-{
- if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
- return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
-
- return false;
-}
-
-static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
-{
- char *uid;
-
- /*
- * UID (ID00, ID01..IDXX) is used for differentiating sockets,
- * read it and strip the "ID" part of it and convert the remaining
- * bytes to integer.
- */
- uid = acpi_device_uid(ACPI_COMPANION(dev));
-
- return kstrtou16(uid + 2, 10, sock_ind);
-}
-
-static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
-{
- struct hsmp_socket *sock = data;
- struct resource r;
-
- switch (res->type) {
- case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
- if (!acpi_dev_resource_memory(res, &r))
- return AE_ERROR;
- if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
- return AE_ERROR;
- sock->mbinfo.base_addr = r.start;
- sock->mbinfo.size = resource_size(&r);
- break;
- case ACPI_RESOURCE_TYPE_END_TAG:
- break;
- default:
- return AE_ERROR;
- }
-
- return AE_OK;
-}
-
-static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
-{
- struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
- union acpi_object *guid, *mailbox_package;
- union acpi_object *dsd;
- acpi_status status;
- int ret = 0;
- int j;
-
- status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
- &buf, ACPI_TYPE_PACKAGE);
- if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
- acpi_format_exception(status));
- return -ENODEV;
- }
-
- dsd = buf.pointer;
-
- /* HSMP _DSD property should contain 2 objects.
- * 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
- * 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
- * This mailbox object contains 3 more acpi objects of type
- * ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
- * these packages inturn contain 2 acpi objects of type
- * ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
- */
- if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
- ret = -EINVAL;
- goto free_buf;
- }
-
- guid = &dsd->package.elements[0];
- mailbox_package = &dsd->package.elements[1];
- if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
- dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
- ret = -EINVAL;
- goto free_buf;
- }
-
- for (j = 0; j < mailbox_package->package.count; j++) {
- union acpi_object *msgobj, *msgstr, *msgint;
-
- msgobj = &mailbox_package->package.elements[j];
- msgstr = &msgobj->package.elements[0];
- msgint = &msgobj->package.elements[1];
-
- /* package should have 1 string and 1 integer object */
- if (msgobj->type != ACPI_TYPE_PACKAGE ||
- msgstr->type != ACPI_TYPE_STRING ||
- msgint->type != ACPI_TYPE_INTEGER) {
- ret = -EINVAL;
- goto free_buf;
- }
-
- if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
- msgstr->string.length)) {
- sock->mbinfo.msg_id_off = msgint->integer.value;
- } else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
- msgstr->string.length)) {
- sock->mbinfo.msg_resp_off = msgint->integer.value;
- } else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
- msgstr->string.length)) {
- sock->mbinfo.msg_arg_off = msgint->integer.value;
- } else {
- ret = -ENOENT;
- goto free_buf;
- }
- }
-
- if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
- !sock->mbinfo.msg_arg_off)
- ret = -EINVAL;
-
-free_buf:
- ACPI_FREE(buf.pointer);
- return ret;
-}
-
-static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
-{
- acpi_status status;
-
- status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
- hsmp_resource, sock);
- if (ACPI_FAILURE(status)) {
- dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
- acpi_format_exception(status));
- return -EINVAL;
- }
- if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
- return -EINVAL;
-
- /* The mapped region should be un cached */
- sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
- sock->mbinfo.size);
- if (!sock->virt_base_addr) {
- dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
- return -ENOMEM;
- }
-
- return 0;
-}
-
-/* Parse the ACPI table to read the data */
-static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
-{
- struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
- int ret;
-
- sock->sock_ind = sock_ind;
- sock->dev = dev;
- plat_dev.is_acpi_device = true;
-
- sema_init(&sock->hsmp_sem, 1);
-
- /* Read MP1 base address from CRS method */
- ret = hsmp_read_acpi_crs(sock);
- if (ret)
- return ret;
-
- /* Read mailbox offsets from DSD table */
- return hsmp_read_acpi_dsd(sock);
-}
-
-static ssize_t hsmp_metric_tbl_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr, char *buf,
- loff_t off, size_t count)
-{
- struct hsmp_socket *sock = bin_attr->private;
- struct hsmp_message msg = { 0 };
- int ret;
-
- if (!sock)
- return -EINVAL;
-
- /* Do not support lseek(), reads entire metric table */
- if (count < bin_attr->size) {
- dev_err(sock->dev, "Wrong buffer size\n");
- return -EINVAL;
- }
-
- msg.msg_id = HSMP_GET_METRIC_TABLE;
- msg.sock_ind = sock->sock_ind;
-
- ret = hsmp_send_message(&msg);
- if (ret)
- return ret;
- memcpy_fromio(buf, sock->metric_tbl_addr, bin_attr->size);
-
- return bin_attr->size;
-}
-
-static int hsmp_get_tbl_dram_base(u16 sock_ind)
-{
- struct hsmp_socket *sock = &plat_dev.sock[sock_ind];
- struct hsmp_message msg = { 0 };
- phys_addr_t dram_addr;
- int ret;
-
- msg.sock_ind = sock_ind;
- msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
- msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
-
- ret = hsmp_send_message(&msg);
- if (ret)
- return ret;
-
- /*
- * calculate the metric table DRAM address from lower and upper 32 bits
- * sent from SMU and ioremap it to virtual address.
- */
- dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
- if (!dram_addr) {
- dev_err(sock->dev, "Invalid DRAM address for metric table\n");
- return -ENOMEM;
- }
- sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
- sizeof(struct hsmp_metric_table));
- if (!sock->metric_tbl_addr) {
- dev_err(sock->dev, "Failed to ioremap metric table addr\n");
- return -ENOMEM;
- }
- return 0;
-}
-
-static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
- struct bin_attribute *battr, int id)
-{
- if (plat_dev.proto_ver == HSMP_PROTO_VER6)
- return battr->attr.mode;
- else
- return 0;
-}
-
-static int hsmp_init_metric_tbl_bin_attr(struct bin_attribute **hattrs, u16 sock_ind)
-{
- struct bin_attribute *hattr = &plat_dev.sock[sock_ind].hsmp_attr;
-
- sysfs_bin_attr_init(hattr);
- hattr->attr.name = HSMP_METRICS_TABLE_NAME;
- hattr->attr.mode = 0444;
- hattr->read = hsmp_metric_tbl_read;
- hattr->size = sizeof(struct hsmp_metric_table);
- hattr->private = &plat_dev.sock[sock_ind];
- hattrs[0] = hattr;
-
- if (plat_dev.proto_ver == HSMP_PROTO_VER6)
- return hsmp_get_tbl_dram_base(sock_ind);
- else
- return 0;
-}
-
-/* One bin sysfs for metrics table */
-#define NUM_HSMP_ATTRS 1
-
-static int hsmp_create_attr_list(struct attribute_group *attr_grp,
- struct device *dev, u16 sock_ind)
-{
- struct bin_attribute **hsmp_bin_attrs;
-
- /* Null terminated list of attributes */
- hsmp_bin_attrs = devm_kcalloc(dev, NUM_HSMP_ATTRS + 1,
- sizeof(*hsmp_bin_attrs),
- GFP_KERNEL);
- if (!hsmp_bin_attrs)
- return -ENOMEM;
-
- attr_grp->bin_attrs = hsmp_bin_attrs;
-
- return hsmp_init_metric_tbl_bin_attr(hsmp_bin_attrs, sock_ind);
-}
-
-static int hsmp_create_non_acpi_sysfs_if(struct device *dev)
-{
- const struct attribute_group **hsmp_attr_grps;
- struct attribute_group *attr_grp;
- u16 i;
-
- hsmp_attr_grps = devm_kcalloc(dev, plat_dev.num_sockets + 1,
- sizeof(*hsmp_attr_grps),
- GFP_KERNEL);
- if (!hsmp_attr_grps)
- return -ENOMEM;
-
- /* Create a sysfs directory for each socket */
- for (i = 0; i < plat_dev.num_sockets; i++) {
- attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group),
- GFP_KERNEL);
- if (!attr_grp)
- return -ENOMEM;
-
- snprintf(plat_dev.sock[i].name, HSMP_ATTR_GRP_NAME_SIZE, "socket%u", (u8)i);
- attr_grp->name = plat_dev.sock[i].name;
- attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
- hsmp_attr_grps[i] = attr_grp;
-
- hsmp_create_attr_list(attr_grp, dev, i);
- }
-
- return devm_device_add_groups(dev, hsmp_attr_grps);
-}
-
-static int hsmp_create_acpi_sysfs_if(struct device *dev)
-{
- struct attribute_group *attr_grp;
- u16 sock_ind;
- int ret;
-
- attr_grp = devm_kzalloc(dev, sizeof(struct attribute_group), GFP_KERNEL);
- if (!attr_grp)
- return -ENOMEM;
-
- attr_grp->is_bin_visible = hsmp_is_sock_attr_visible;
-
- ret = hsmp_get_uid(dev, &sock_ind);
- if (ret)
- return ret;
-
- ret = hsmp_create_attr_list(attr_grp, dev, sock_ind);
- if (ret)
- return ret;
-
- return devm_device_add_group(dev, attr_grp);
-}
-
-static int hsmp_cache_proto_ver(u16 sock_ind)
-{
- struct hsmp_message msg = { 0 };
- int ret;
-
- msg.msg_id = HSMP_GET_PROTO_VER;
- msg.sock_ind = sock_ind;
- msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
-
- ret = hsmp_send_message(&msg);
- if (!ret)
- plat_dev.proto_ver = msg.args[0];
-
- return ret;
-}
-
-static inline bool is_f1a_m0h(void)
-{
- if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
- return true;
-
- return false;
-}
-
-static int init_platform_device(struct device *dev)
-{
- struct hsmp_socket *sock;
- int ret, i;
-
- for (i = 0; i < plat_dev.num_sockets; i++) {
- if (!node_to_amd_nb(i))
- return -ENODEV;
- sock = &plat_dev.sock[i];
- sock->root = node_to_amd_nb(i)->root;
- sock->sock_ind = i;
- sock->dev = dev;
- sock->mbinfo.base_addr = SMN_HSMP_BASE;
-
- /*
- * This is a transitional change from non-ACPI to ACPI, only
- * family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
- */
- if (is_f1a_m0h())
- sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
- else
- sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
-
- sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
- sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
- sema_init(&sock->hsmp_sem, 1);
-
- /* Test the hsmp interface on each socket */
- ret = hsmp_test(i, 0xDEADBEEF);
- if (ret) {
- dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
- boot_cpu_data.x86, boot_cpu_data.x86_model);
- dev_err(dev, "Is HSMP disabled in BIOS ?\n");
- return ret;
- }
- }
-
- return 0;
-}
-
-static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
- {ACPI_HSMP_DEVICE_HID, 0},
- {}
-};
-MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
-
-static int hsmp_pltdrv_probe(struct platform_device *pdev)
-{
- struct acpi_device *adev;
- u16 sock_ind = 0;
- int ret;
-
- /*
- * On ACPI supported BIOS, there is an ACPI HSMP device added for
- * each socket, so the per socket probing, but the memory allocated for
- * sockets should be contiguous to access it as an array,
- * Hence allocate memory for all the sockets at once instead of allocating
- * on each probe.
- */
- if (!plat_dev.is_probed) {
- plat_dev.sock = devm_kcalloc(&pdev->dev, plat_dev.num_sockets,
- sizeof(*plat_dev.sock),
- GFP_KERNEL);
- if (!plat_dev.sock)
- return -ENOMEM;
- }
- adev = ACPI_COMPANION(&pdev->dev);
- if (adev && !acpi_match_device_ids(adev, amd_hsmp_acpi_ids)) {
- ret = hsmp_get_uid(&pdev->dev, &sock_ind);
- if (ret)
- return ret;
- if (sock_ind >= plat_dev.num_sockets)
- return -EINVAL;
- ret = hsmp_parse_acpi_table(&pdev->dev, sock_ind);
- if (ret) {
- dev_err(&pdev->dev, "Failed to parse ACPI table\n");
- return ret;
- }
- /* Test the hsmp interface */
- ret = hsmp_test(sock_ind, 0xDEADBEEF);
- if (ret) {
- dev_err(&pdev->dev, "HSMP test message failed on Fam:%x model:%x\n",
- boot_cpu_data.x86, boot_cpu_data.x86_model);
- dev_err(&pdev->dev, "Is HSMP disabled in BIOS ?\n");
- return ret;
- }
- } else {
- ret = init_platform_device(&pdev->dev);
- if (ret) {
- dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
- return ret;
- }
- }
-
- ret = hsmp_cache_proto_ver(sock_ind);
- if (ret) {
- dev_err(&pdev->dev, "Failed to read HSMP protocol version\n");
- return ret;
- }
-
- if (plat_dev.is_acpi_device)
- ret = hsmp_create_acpi_sysfs_if(&pdev->dev);
- else
- ret = hsmp_create_non_acpi_sysfs_if(&pdev->dev);
- if (ret)
- dev_err(&pdev->dev, "Failed to create HSMP sysfs interface\n");
-
- if (!plat_dev.is_probed) {
- plat_dev.hsmp_device.name = HSMP_CDEV_NAME;
- plat_dev.hsmp_device.minor = MISC_DYNAMIC_MINOR;
- plat_dev.hsmp_device.fops = &hsmp_fops;
- plat_dev.hsmp_device.parent = &pdev->dev;
- plat_dev.hsmp_device.nodename = HSMP_DEVNODE_NAME;
- plat_dev.hsmp_device.mode = 0644;
-
- ret = misc_register(&plat_dev.hsmp_device);
- if (ret)
- return ret;
-
- plat_dev.is_probed = true;
- }
-
- return 0;
-
-}
-
-static void hsmp_pltdrv_remove(struct platform_device *pdev)
-{
- /*
- * We register only one misc_device even on multi socket system.
- * So, deregister should happen only once.
- */
- if (plat_dev.is_probed) {
- misc_deregister(&plat_dev.hsmp_device);
- plat_dev.is_probed = false;
- }
-}
-
-static struct platform_driver amd_hsmp_driver = {
- .probe = hsmp_pltdrv_probe,
- .remove_new = hsmp_pltdrv_remove,
- .driver = {
- .name = DRIVER_NAME,
- .acpi_match_table = amd_hsmp_acpi_ids,
- },
-};
-
-static struct platform_device *amd_hsmp_platdev;
-
-static int hsmp_plat_dev_register(void)
-{
- int ret;
-
- amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
- if (!amd_hsmp_platdev)
- return -ENOMEM;
-
- ret = platform_device_add(amd_hsmp_platdev);
- if (ret)
- platform_device_put(amd_hsmp_platdev);
-
- return ret;
-}
-
-static int __init hsmp_plt_init(void)
-{
- int ret = -ENODEV;
-
- if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) {
- pr_err("HSMP is not supported on Family:%x model:%x\n",
- boot_cpu_data.x86, boot_cpu_data.x86_model);
- return ret;
- }
-
- /*
- * amd_nb_num() returns number of SMN/DF interfaces present in the system
- * if we have N SMN/DF interfaces that ideally means N sockets
- */
- plat_dev.num_sockets = amd_nb_num();
- if (plat_dev.num_sockets == 0 || plat_dev.num_sockets > MAX_AMD_SOCKETS)
- return ret;
-
- ret = platform_driver_register(&amd_hsmp_driver);
- if (ret)
- return ret;
-
- if (!plat_dev.is_acpi_device) {
- ret = hsmp_plat_dev_register();
- if (ret)
- platform_driver_unregister(&amd_hsmp_driver);
- }
-
- return ret;
-}
-
-static void __exit hsmp_plt_exit(void)
-{
- platform_device_unregister(amd_hsmp_platdev);
- platform_driver_unregister(&amd_hsmp_driver);
-}
-
-device_initcall(hsmp_plt_init);
-module_exit(hsmp_plt_exit);
-
-MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
-MODULE_VERSION(DRIVER_VERSION);
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/amd/hsmp/Kconfig b/drivers/platform/x86/amd/hsmp/Kconfig
new file mode 100644
index 000000000000..2911120792e8
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD HSMP Driver
+#
+
+config AMD_HSMP
+ tristate
+
+menu "AMD HSMP Driver"
+ depends on AMD_NODE || COMPILE_TEST
+
+config AMD_HSMP_ACPI
+ tristate "AMD HSMP ACPI device driver"
+ depends on ACPI
+ depends on HWMON || !HWMON
+ select AMD_HSMP
+ help
+ Host System Management Port (HSMP) interface is a mailbox interface
+ between the x86 core and the System Management Unit (SMU) firmware.
+ The driver provides a way for user space tools to monitor and manage
+ system management functionality on EPYC and MI300A server CPUs
+ from AMD.
+
+ This option supports ACPI based probing.
+ You may enable this, if your platform BIOS provides an ACPI object
+ as described in amd_hsmp.rst document.
+
+ If you choose to compile this driver as a module the module will be
+ called hsmp_acpi.
+
+config AMD_HSMP_PLAT
+ tristate "AMD HSMP platform device driver"
+ depends on HWMON || !HWMON
+ select AMD_HSMP
+ help
+ Host System Management Port (HSMP) interface is a mailbox interface
+ between the x86 core and the System Management Unit (SMU) firmware.
+ The driver provides a way for user space tools to monitor and manage
+ system management functionality on EPYC and MI300A server CPUs
+ from AMD.
+
+ This option supports platform device based probing.
+ You may enable this, if your platform BIOS does not provide
+ HSMP ACPI object.
+
+ If you choose to compile this driver as a module the module will be
+ called amd_hsmp.
+
+endmenu
diff --git a/drivers/platform/x86/amd/hsmp/Makefile b/drivers/platform/x86/amd/hsmp/Makefile
new file mode 100644
index 000000000000..ce8342e71f50
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/Makefile
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for drivers/platform/x86/amd/hsmp
+# AMD HSMP Driver
+#
+
+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
+hsmp_acpi-y := acpi.o
diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
new file mode 100644
index 000000000000..2f1faa82d13e
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -0,0 +1,643 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD HSMP Platform Driver
+ * Copyright (c) 2024, AMD.
+ * All Rights Reserved.
+ *
+ * This file provides an ACPI based driver implementation for HSMP interface.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <asm/amd/hsmp.h>
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/ioport.h>
+#include <linux/kstrtox.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+#include <linux/uuid.h>
+
+#include <uapi/asm-generic/errno-base.h>
+
+#include <asm/amd/node.h>
+
+#include "hsmp.h"
+
+#define DRIVER_NAME "hsmp_acpi"
+
+/* These are the strings specified in ACPI table */
+#define MSG_IDOFF_STR "MsgIdOffset"
+#define MSG_ARGOFF_STR "MsgArgOffset"
+#define MSG_RESPOFF_STR "MsgRspOffset"
+
+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)
+{
+ if (write)
+ iowrite32(*value, sock->virt_base_addr + offset);
+ else
+ *value = ioread32(sock->virt_base_addr + offset);
+
+ return 0;
+}
+
+/* This is the UUID used for HSMP */
+static const guid_t acpi_hsmp_uuid = GUID_INIT(0xb74d619d, 0x5707, 0x48bd,
+ 0xa6, 0x9f, 0x4e, 0xa2,
+ 0x87, 0x1f, 0xc2, 0xf6);
+
+static inline bool is_acpi_hsmp_uuid(union acpi_object *obj)
+{
+ if (obj->type == ACPI_TYPE_BUFFER && obj->buffer.length == UUID_SIZE)
+ return guid_equal((guid_t *)obj->buffer.pointer, &acpi_hsmp_uuid);
+
+ return false;
+}
+
+static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind)
+{
+ char *uid;
+
+ /*
+ * UID (ID00, ID01..IDXX) is used for differentiating sockets,
+ * read it and strip the "ID" part of it and convert the remaining
+ * bytes to integer.
+ */
+ uid = acpi_device_uid(ACPI_COMPANION(dev));
+
+ return kstrtou16(uid + 2, 10, sock_ind);
+}
+
+static acpi_status hsmp_resource(struct acpi_resource *res, void *data)
+{
+ struct hsmp_socket *sock = data;
+ struct resource r;
+
+ switch (res->type) {
+ case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
+ if (!acpi_dev_resource_memory(res, &r))
+ return AE_ERROR;
+ if (!r.start || r.end < r.start || !(r.flags & IORESOURCE_MEM_WRITEABLE))
+ return AE_ERROR;
+ sock->mbinfo.base_addr = r.start;
+ sock->mbinfo.size = resource_size(&r);
+ break;
+ case ACPI_RESOURCE_TYPE_END_TAG:
+ break;
+ default:
+ return AE_ERROR;
+ }
+
+ return AE_OK;
+}
+
+static int hsmp_read_acpi_dsd(struct hsmp_socket *sock)
+{
+ struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
+ union acpi_object *guid, *mailbox_package;
+ union acpi_object *dsd;
+ acpi_status status;
+ int ret = 0;
+ int j;
+
+ status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL,
+ &buf, ACPI_TYPE_PACKAGE);
+ if (ACPI_FAILURE(status)) {
+ dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n",
+ acpi_format_exception(status));
+ return -ENODEV;
+ }
+
+ dsd = buf.pointer;
+
+ /* HSMP _DSD property should contain 2 objects.
+ * 1. guid which is an acpi object of type ACPI_TYPE_BUFFER
+ * 2. mailbox which is an acpi object of type ACPI_TYPE_PACKAGE
+ * This mailbox object contains 3 more acpi objects of type
+ * ACPI_TYPE_PACKAGE for holding msgid, msgresp, msgarg offsets
+ * these packages inturn contain 2 acpi objects of type
+ * ACPI_TYPE_STRING and ACPI_TYPE_INTEGER
+ */
+ if (!dsd || dsd->type != ACPI_TYPE_PACKAGE || dsd->package.count != 2) {
+ ret = -EINVAL;
+ goto free_buf;
+ }
+
+ guid = &dsd->package.elements[0];
+ mailbox_package = &dsd->package.elements[1];
+ if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) {
+ dev_err(sock->dev, "Invalid hsmp _DSD table data\n");
+ ret = -EINVAL;
+ goto free_buf;
+ }
+
+ for (j = 0; j < mailbox_package->package.count; j++) {
+ union acpi_object *msgobj, *msgstr, *msgint;
+
+ msgobj = &mailbox_package->package.elements[j];
+ msgstr = &msgobj->package.elements[0];
+ msgint = &msgobj->package.elements[1];
+
+ /* package should have 1 string and 1 integer object */
+ if (msgobj->type != ACPI_TYPE_PACKAGE ||
+ msgstr->type != ACPI_TYPE_STRING ||
+ msgint->type != ACPI_TYPE_INTEGER) {
+ ret = -EINVAL;
+ goto free_buf;
+ }
+
+ if (!strncmp(msgstr->string.pointer, MSG_IDOFF_STR,
+ msgstr->string.length)) {
+ sock->mbinfo.msg_id_off = msgint->integer.value;
+ } else if (!strncmp(msgstr->string.pointer, MSG_RESPOFF_STR,
+ msgstr->string.length)) {
+ sock->mbinfo.msg_resp_off = msgint->integer.value;
+ } else if (!strncmp(msgstr->string.pointer, MSG_ARGOFF_STR,
+ msgstr->string.length)) {
+ sock->mbinfo.msg_arg_off = msgint->integer.value;
+ } else {
+ ret = -ENOENT;
+ goto free_buf;
+ }
+ }
+
+ if (!sock->mbinfo.msg_id_off || !sock->mbinfo.msg_resp_off ||
+ !sock->mbinfo.msg_arg_off)
+ ret = -EINVAL;
+
+free_buf:
+ ACPI_FREE(buf.pointer);
+ return ret;
+}
+
+static int hsmp_read_acpi_crs(struct hsmp_socket *sock)
+{
+ acpi_status status;
+
+ status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS,
+ hsmp_resource, sock);
+ if (ACPI_FAILURE(status)) {
+ dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n",
+ acpi_format_exception(status));
+ return -EINVAL;
+ }
+ if (!sock->mbinfo.base_addr || !sock->mbinfo.size)
+ return -EINVAL;
+
+ /* The mapped region should be un-cached */
+ sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr,
+ sock->mbinfo.size);
+ if (!sock->virt_base_addr) {
+ dev_err(sock->dev, "Failed to ioremap MP1 base address\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* Parse the ACPI table to read the data */
+static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind)
+{
+ struct hsmp_socket *sock = &hsmp_pdev->sock[sock_ind];
+ int ret;
+
+ sock->sock_ind = sock_ind;
+ sock->dev = dev;
+ sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr;
+
+ sema_init(&sock->hsmp_sem, 1);
+
+ dev_set_drvdata(dev, sock);
+
+ /* Read MP1 base address from CRS method */
+ ret = hsmp_read_acpi_crs(sock);
+ if (ret)
+ return ret;
+
+ /* Read mailbox offsets from DSD table */
+ return hsmp_read_acpi_dsd(sock);
+}
+
+static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct hsmp_socket *sock = dev_get_drvdata(dev);
+
+ return hsmp_metric_tbl_read(sock, buf, count);
+}
+
+static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
+ const struct bin_attribute *battr, int id)
+{
+ if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
+ return battr->attr.mode;
+
+ 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;
+ int ret;
+
+ ret = hsmp_get_uid(dev, &sock_ind);
+ if (ret)
+ return ret;
+ if (sock_ind >= hsmp_pdev->num_sockets)
+ return -EINVAL;
+
+ ret = hsmp_parse_acpi_table(dev, sock_ind);
+ if (ret) {
+ dev_err(dev, "Failed to parse ACPI table\n");
+ return ret;
+ }
+
+ /* Test the hsmp interface */
+ ret = hsmp_test(sock_ind, 0xDEADBEEF);
+ if (ret) {
+ dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
+ boot_cpu_data.x86, boot_cpu_data.x86_model);
+ dev_err(dev, "Is HSMP disabled in BIOS ?\n");
+ return ret;
+ }
+
+ ret = hsmp_cache_proto_ver(sock_ind);
+ if (ret) {
+ dev_err(dev, "Failed to read HSMP protocol version\n");
+ return ret;
+ }
+
+ if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
+ ret = hsmp_get_tbl_dram_base(sock_ind);
+ if (ret)
+ 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");
+
+ dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]);
+
+ return ret;
+}
+
+static const struct bin_attribute hsmp_metric_tbl_attr = {
+ .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
+ .read_new = hsmp_metric_tbl_acpi_read,
+ .size = sizeof(struct hsmp_metric_table),
+};
+
+static const struct bin_attribute *hsmp_attr_list[] = {
+ &hsmp_metric_tbl_attr,
+ 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[] = {
+ &hsmp_attr_grp,
+ NULL
+};
+
+static const struct acpi_device_id amd_hsmp_acpi_ids[] = {
+ {ACPI_HSMP_DEVICE_HID, 0},
+ {}
+};
+MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
+
+static int hsmp_acpi_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ hsmp_pdev = get_hsmp_pdev();
+ if (!hsmp_pdev)
+ return -ENOMEM;
+
+ if (!hsmp_pdev->is_probed) {
+ hsmp_pdev->num_sockets = amd_num_nodes();
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
+ return -ENODEV;
+
+ hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
+ sizeof(*hsmp_pdev->sock),
+ GFP_KERNEL);
+ if (!hsmp_pdev->sock)
+ return -ENOMEM;
+ }
+
+ ret = init_acpi(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n");
+ return ret;
+ }
+
+ if (!hsmp_pdev->is_probed) {
+ ret = hsmp_misc_register(&pdev->dev);
+ if (ret)
+ return ret;
+ hsmp_pdev->is_probed = true;
+ }
+
+ return 0;
+}
+
+static void hsmp_acpi_remove(struct platform_device *pdev)
+{
+ /*
+ * We register only one misc_device even on multi-socket system.
+ * So, deregister should happen only once.
+ */
+ if (hsmp_pdev->is_probed) {
+ hsmp_misc_deregister();
+ hsmp_pdev->is_probed = false;
+ }
+}
+
+static struct platform_driver amd_hsmp_driver = {
+ .probe = hsmp_acpi_probe,
+ .remove = hsmp_acpi_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .acpi_match_table = amd_hsmp_acpi_ids,
+ .dev_groups = hsmp_groups,
+ },
+};
+
+module_platform_driver(amd_hsmp_driver);
+
+MODULE_IMPORT_NS("AMD_HSMP");
+MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
new file mode 100644
index 000000000000..538b36b97095
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD HSMP Platform Driver
+ * Copyright (c) 2022, AMD.
+ * All Rights Reserved.
+ *
+ * This file provides a device implementation for HSMP interface
+ */
+
+#include <asm/amd/hsmp.h>
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/semaphore.h>
+#include <linux/sysfs.h>
+
+#include "hsmp.h"
+
+/* HSMP Status / Error codes */
+#define HSMP_STATUS_NOT_READY 0x00
+#define HSMP_STATUS_OK 0x01
+#define HSMP_ERR_INVALID_MSG 0xFE
+#define HSMP_ERR_INVALID_INPUT 0xFF
+#define HSMP_ERR_PREREQ_NOT_SATISFIED 0xFD
+#define HSMP_ERR_SMU_BUSY 0xFC
+
+/* Timeout in millsec */
+#define HSMP_MSG_TIMEOUT 100
+#define HSMP_SHORT_SLEEP 1
+
+#define HSMP_WR true
+#define HSMP_RD false
+
+/*
+ * When same message numbers are used for both GET and SET operation,
+ * bit:31 indicates whether its SET or GET operation.
+ */
+#define CHECK_GET_BIT BIT(31)
+
+static struct hsmp_plat_device hsmp_pdev;
+
+/*
+ * Send a message to the HSMP port via PCI-e config space registers
+ * or by writing to MMIO space.
+ *
+ * The caller is expected to zero out any unused arguments.
+ * If a response is expected, the number of response words should be greater than 0.
+ *
+ * Returns 0 for success and populates the requested number of arguments.
+ * Returns a negative error code for failure.
+ */
+static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *msg)
+{
+ struct hsmp_mbaddr_info *mbinfo;
+ unsigned long timeout, short_sleep;
+ u32 mbox_status;
+ u32 index;
+ int ret;
+
+ mbinfo = &sock->mbinfo;
+
+ /* Clear the status register */
+ mbox_status = HSMP_STATUS_NOT_READY;
+ ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_WR);
+ if (ret) {
+ dev_err(sock->dev, "Error %d clearing mailbox status register\n", ret);
+ return ret;
+ }
+
+ index = 0;
+ /* Write any message arguments */
+ while (index < msg->num_args) {
+ ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
+ &msg->args[index], HSMP_WR);
+ if (ret) {
+ dev_err(sock->dev, "Error %d writing message argument %d\n", ret, index);
+ return ret;
+ }
+ index++;
+ }
+
+ /* Write the message ID which starts the operation */
+ ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_id_off, &msg->msg_id, HSMP_WR);
+ if (ret) {
+ dev_err(sock->dev, "Error %d writing message ID %u\n", ret, msg->msg_id);
+ return ret;
+ }
+
+ /*
+ * Depending on when the trigger write completes relative to the SMU
+ * firmware 1 ms cycle, the operation may take from tens of us to 1 ms
+ * to complete. Some operations may take more. Therefore we will try
+ * a few short duration sleeps and switch to long sleeps if we don't
+ * succeed quickly.
+ */
+ short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP);
+ timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT);
+
+ while (time_before(jiffies, timeout)) {
+ ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_resp_off, &mbox_status, HSMP_RD);
+ if (ret) {
+ dev_err(sock->dev, "Error %d reading mailbox status\n", ret);
+ return ret;
+ }
+
+ if (mbox_status != HSMP_STATUS_NOT_READY)
+ break;
+ if (time_before(jiffies, short_sleep))
+ usleep_range(50, 100);
+ else
+ usleep_range(1000, 2000);
+ }
+
+ if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) {
+ dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -ETIMEDOUT;
+ } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) {
+ dev_err(sock->dev, "Message ID 0x%X failure : Invalid message (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -ENOMSG;
+ } else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) {
+ dev_err(sock->dev, "Message ID 0x%X failure : Invalid arguments (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -EINVAL;
+ } else if (unlikely(mbox_status == HSMP_ERR_PREREQ_NOT_SATISFIED)) {
+ dev_err(sock->dev, "Message ID 0x%X failure : Prerequisite not satisfied (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -EREMOTEIO;
+ } else if (unlikely(mbox_status == HSMP_ERR_SMU_BUSY)) {
+ dev_err(sock->dev, "Message ID 0x%X failure : SMU BUSY (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -EBUSY;
+ } else if (unlikely(mbox_status != HSMP_STATUS_OK)) {
+ dev_err(sock->dev, "Message ID 0x%X unknown failure (status = 0x%X)\n",
+ msg->msg_id, mbox_status);
+ return -EIO;
+ }
+
+ /*
+ * SMU has responded OK. Read response data.
+ * SMU reads the input arguments from eight 32 bit registers starting
+ * from SMN_HSMP_MSG_DATA and writes the response data to the same
+ * SMN_HSMP_MSG_DATA address.
+ * We copy the response data if any, back to the args[].
+ */
+ index = 0;
+ while (index < msg->response_sz) {
+ ret = sock->amd_hsmp_rdwr(sock, mbinfo->msg_arg_off + (index << 2),
+ &msg->args[index], HSMP_RD);
+ if (ret) {
+ dev_err(sock->dev, "Error %d reading response %u for message ID:%u\n",
+ ret, index, msg->msg_id);
+ break;
+ }
+ index++;
+ }
+
+ return ret;
+}
+
+static int validate_message(struct hsmp_message *msg)
+{
+ /* msg_id against valid range of message IDs */
+ if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX)
+ return -ENOMSG;
+
+ /* msg_id is a reserved message ID */
+ if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD)
+ return -ENOMSG;
+
+ /*
+ * num_args passed by user should match the num_args specified in
+ * message description table.
+ */
+ if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args)
+ return -EINVAL;
+
+ /*
+ * Some older HSMP SET messages are updated to add GET in the same message.
+ * In these messages, GET returns the current value and SET also returns
+ * the successfully set value. To support this GET and SET in same message
+ * while maintaining backward compatibility for the HSMP users,
+ * hsmp_msg_desc_table[] indicates only maximum allowed response_sz.
+ */
+ if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) {
+ if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz)
+ return -EINVAL;
+ } else {
+ /* only HSMP_SET or HSMP_GET messages go through this strict check */
+ if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int hsmp_send_message(struct hsmp_message *msg)
+{
+ struct hsmp_socket *sock;
+ int ret;
+
+ if (!msg)
+ return -EINVAL;
+ ret = validate_message(msg);
+ if (ret)
+ return ret;
+
+ if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets)
+ return -ENODEV;
+ sock = &hsmp_pdev.sock[msg->sock_ind];
+
+ /*
+ * The time taken by smu operation to complete is between
+ * 10us to 1ms. Sometime it may take more time.
+ * In SMP system timeout of 100 millisecs should
+ * be enough for the previous thread to finish the operation
+ */
+ ret = down_timeout(&sock->hsmp_sem, msecs_to_jiffies(HSMP_MSG_TIMEOUT));
+ if (ret < 0)
+ return ret;
+
+ ret = __hsmp_send_message(sock, msg);
+
+ up(&sock->hsmp_sem);
+
+ return ret;
+}
+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 };
+ int ret;
+
+ /*
+ * Test the hsmp port by performing TEST command. The test message
+ * takes one argument and returns the value of that argument + 1.
+ */
+ msg.msg_id = HSMP_TEST;
+ msg.num_args = 1;
+ msg.response_sz = 1;
+ msg.args[0] = value;
+ msg.sock_ind = sock_ind;
+
+ ret = hsmp_send_message(&msg);
+ if (ret)
+ return ret;
+
+ /* Check the response value */
+ if (msg.args[0] != (value + 1)) {
+ dev_err(hsmp_pdev.sock[sock_ind].dev,
+ "Socket %d test message failed, Expected 0x%08X, received 0x%08X\n",
+ sock_ind, (value + 1), msg.args[0]);
+ return -EBADE;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_test, "AMD_HSMP");
+
+static bool is_get_msg(struct hsmp_message *msg)
+{
+ if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_GET)
+ return true;
+
+ if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET &&
+ (msg->args[0] & CHECK_GET_BIT))
+ return true;
+
+ return false;
+}
+
+long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ int __user *arguser = (int __user *)arg;
+ struct hsmp_message msg = { 0 };
+ int ret;
+
+ if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message)))
+ return -EFAULT;
+
+ /*
+ * Check msg_id is within the range of supported msg ids
+ * i.e within the array bounds of hsmp_msg_desc_table
+ */
+ if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX)
+ return -ENOMSG;
+
+ switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) {
+ case FMODE_WRITE:
+ /*
+ * Device is opened in O_WRONLY mode
+ * Execute only set/configure commands
+ */
+ if (is_get_msg(&msg))
+ return -EPERM;
+ break;
+ case FMODE_READ:
+ /*
+ * Device is opened in O_RDONLY mode
+ * Execute only get/monitor commands
+ */
+ if (!is_get_msg(&msg))
+ return -EPERM;
+ break;
+ case FMODE_READ | FMODE_WRITE:
+ /*
+ * Device is opened in O_RDWR mode
+ * Execute both get/monitor and set/configure commands
+ */
+ break;
+ default:
+ return -EPERM;
+ }
+
+ ret = hsmp_send_message(&msg);
+ if (ret)
+ return ret;
+
+ if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) {
+ /* Copy results back to user for get/monitor commands */
+ if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size)
+{
+ struct hsmp_message msg = { 0 };
+ int ret;
+
+ if (!sock || !buf)
+ return -EINVAL;
+
+ /* Do not support lseek(), also don't allow more than the size of metric table */
+ if (size != sizeof(struct hsmp_metric_table)) {
+ dev_err(sock->dev, "Wrong buffer size\n");
+ return -EINVAL;
+ }
+
+ msg.msg_id = HSMP_GET_METRIC_TABLE;
+ msg.sock_ind = sock->sock_ind;
+
+ ret = hsmp_send_message(&msg);
+ if (ret)
+ return ret;
+ memcpy_fromio(buf, sock->metric_tbl_addr, size);
+
+ return size;
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP");
+
+int hsmp_get_tbl_dram_base(u16 sock_ind)
+{
+ struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind];
+ struct hsmp_message msg = { 0 };
+ phys_addr_t dram_addr;
+ int ret;
+
+ msg.sock_ind = sock_ind;
+ msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz;
+ msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR;
+
+ ret = hsmp_send_message(&msg);
+ if (ret)
+ return ret;
+
+ /*
+ * calculate the metric table DRAM address from lower and upper 32 bits
+ * sent from SMU and ioremap it to virtual address.
+ */
+ dram_addr = msg.args[0] | ((u64)(msg.args[1]) << 32);
+ if (!dram_addr) {
+ dev_err(sock->dev, "Invalid DRAM address for metric table\n");
+ return -ENOMEM;
+ }
+ sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr,
+ sizeof(struct hsmp_metric_table));
+ if (!sock->metric_tbl_addr) {
+ dev_err(sock->dev, "Failed to ioremap metric table addr\n");
+ return -ENOMEM;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_get_tbl_dram_base, "AMD_HSMP");
+
+int hsmp_cache_proto_ver(u16 sock_ind)
+{
+ struct hsmp_message msg = { 0 };
+ int ret;
+
+ msg.msg_id = HSMP_GET_PROTO_VER;
+ msg.sock_ind = sock_ind;
+ msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz;
+
+ ret = hsmp_send_message(&msg);
+ if (!ret)
+ hsmp_pdev.proto_ver = msg.args[0];
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_cache_proto_ver, "AMD_HSMP");
+
+static const struct file_operations hsmp_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = hsmp_ioctl,
+ .compat_ioctl = hsmp_ioctl,
+};
+
+int hsmp_misc_register(struct device *dev)
+{
+ hsmp_pdev.mdev.name = HSMP_CDEV_NAME;
+ hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR;
+ hsmp_pdev.mdev.fops = &hsmp_fops;
+ hsmp_pdev.mdev.parent = dev;
+ hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME;
+ hsmp_pdev.mdev.mode = 0644;
+
+ return misc_register(&hsmp_pdev.mdev);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, "AMD_HSMP");
+
+void hsmp_misc_deregister(void)
+{
+ misc_deregister(&hsmp_pdev.mdev);
+}
+EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP");
+
+struct hsmp_plat_device *get_hsmp_pdev(void)
+{
+ return &hsmp_pdev;
+}
+EXPORT_SYMBOL_NS_GPL(get_hsmp_pdev, "AMD_HSMP");
+
+MODULE_DESCRIPTION("AMD HSMP Common driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
new file mode 100644
index 000000000000..36b5ceea9ac0
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD HSMP Platform Driver
+ * Copyright (c) 2024, AMD.
+ * All Rights Reserved.
+ *
+ * Header file for HSMP driver
+ */
+
+#ifndef HSMP_H
+#define HSMP_H
+
+#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>
+#include <linux/sysfs.h>
+
+#define HSMP_METRICS_TABLE_NAME "metrics_bin"
+
+#define HSMP_ATTR_GRP_NAME_SIZE 10
+
+#define HSMP_CDEV_NAME "hsmp_cdev"
+#define HSMP_DEVNODE_NAME "hsmp"
+#define ACPI_HSMP_DEVICE_HID "AMDI0097"
+
+#define DRIVER_VERSION "2.5"
+
+struct hsmp_mbaddr_info {
+ u32 base_addr;
+ u32 msg_id_off;
+ u32 msg_resp_off;
+ u32 msg_arg_off;
+ u32 size;
+};
+
+struct hsmp_socket {
+ struct bin_attribute hsmp_attr;
+ struct hsmp_mbaddr_info mbinfo;
+ void __iomem *metric_tbl_addr;
+ void __iomem *virt_base_addr;
+ struct semaphore hsmp_sem;
+ char name[HSMP_ATTR_GRP_NAME_SIZE];
+ struct device *dev;
+ u16 sock_ind;
+ int (*amd_hsmp_rdwr)(struct hsmp_socket *sock, u32 off, u32 *val, bool rw);
+};
+
+struct hsmp_plat_device {
+ struct miscdevice mdev;
+ struct hsmp_socket *sock;
+ u32 proto_ver;
+ u16 num_sockets;
+ bool is_probed;
+};
+
+int hsmp_cache_proto_ver(u16 sock_ind);
+int hsmp_test(u16 sock_ind, u32 value);
+long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg);
+void hsmp_misc_deregister(void);
+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
+int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args);
+#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..0cc9a742497f
--- /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 <asm/amd/hsmp.h>
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#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
new file mode 100644
index 000000000000..e3874c47ed9e
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD HSMP Platform Driver
+ * Copyright (c) 2024, AMD.
+ * All Rights Reserved.
+ *
+ * This file provides platform device implementations.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <asm/amd/hsmp.h>
+
+#include <linux/acpi.h>
+#include <linux/build_bug.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+#include <asm/amd/node.h>
+
+#include "hsmp.h"
+
+#define DRIVER_NAME "amd_hsmp"
+
+/*
+ * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
+ * register into the SMN_INDEX register, and reads/writes the SMN_DATA reg.
+ * Below are required SMN address for HSMP Mailbox register offsets in SMU address space
+ */
+#define SMN_HSMP_BASE 0x3B00000
+#define SMN_HSMP_MSG_ID 0x0010534
+#define SMN_HSMP_MSG_ID_F1A_M0H 0x0010934
+#define SMN_HSMP_MSG_RESP 0x0010980
+#define SMN_HSMP_MSG_DATA 0x00109E0
+
+static struct hsmp_plat_device *hsmp_pdev;
+
+static int amd_hsmp_pci_rdwr(struct hsmp_socket *sock, u32 offset,
+ u32 *value, bool write)
+{
+ return amd_smn_hsmp_rdwr(sock->sock_ind, sock->mbinfo.base_addr + offset, value, write);
+}
+
+static ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct hsmp_socket *sock;
+ u16 sock_ind;
+
+ sock_ind = (uintptr_t)bin_attr->private;
+ if (sock_ind >= hsmp_pdev->num_sockets)
+ return -EINVAL;
+
+ sock = &hsmp_pdev->sock[sock_ind];
+
+ return hsmp_metric_tbl_read(sock, buf, count);
+}
+
+static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj,
+ const struct bin_attribute *battr, int id)
+{
+ u16 sock_ind;
+
+ sock_ind = (uintptr_t)battr->private;
+
+ if (id == 0 && sock_ind >= hsmp_pdev->num_sockets)
+ return SYSFS_GROUP_INVISIBLE;
+
+ if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
+ return battr->attr.mode;
+
+ return 0;
+}
+
+/*
+ * AMD supports maximum of 8 sockets in a system.
+ * Static array of 8 + 1(for NULL) elements is created below
+ * to create sysfs groups for sockets.
+ * is_bin_visible function is used to show / hide the necessary groups.
+ *
+ * Validate the maximum number against MAX_AMD_NUM_NODES. If this changes,
+ * then the attributes and groups below must be adjusted.
+ */
+static_assert(MAX_AMD_NUM_NODES == 8);
+
+#define HSMP_BIN_ATTR(index, _list) \
+static const struct bin_attribute attr##index = { \
+ .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
+ .private = (void *)index, \
+ .read_new = hsmp_metric_tbl_plat_read, \
+ .size = sizeof(struct hsmp_metric_table), \
+}; \
+static const struct bin_attribute _list[] = { \
+ &attr##index, \
+ NULL \
+}
+
+HSMP_BIN_ATTR(0, *sock0_attr_list);
+HSMP_BIN_ATTR(1, *sock1_attr_list);
+HSMP_BIN_ATTR(2, *sock2_attr_list);
+HSMP_BIN_ATTR(3, *sock3_attr_list);
+HSMP_BIN_ATTR(4, *sock4_attr_list);
+HSMP_BIN_ATTR(5, *sock5_attr_list);
+HSMP_BIN_ATTR(6, *sock6_attr_list);
+HSMP_BIN_ATTR(7, *sock7_attr_list);
+
+#define HSMP_BIN_ATTR_GRP(index, _list, _name) \
+static const struct attribute_group sock##index##_attr_grp = { \
+ .bin_attrs_new = _list, \
+ .is_bin_visible = hsmp_is_sock_attr_visible, \
+ .name = #_name, \
+}
+
+HSMP_BIN_ATTR_GRP(0, sock0_attr_list, socket0);
+HSMP_BIN_ATTR_GRP(1, sock1_attr_list, socket1);
+HSMP_BIN_ATTR_GRP(2, sock2_attr_list, socket2);
+HSMP_BIN_ATTR_GRP(3, sock3_attr_list, socket3);
+HSMP_BIN_ATTR_GRP(4, sock4_attr_list, socket4);
+HSMP_BIN_ATTR_GRP(5, sock5_attr_list, socket5);
+HSMP_BIN_ATTR_GRP(6, sock6_attr_list, socket6);
+HSMP_BIN_ATTR_GRP(7, sock7_attr_list, socket7);
+
+static const struct attribute_group *hsmp_groups[] = {
+ &sock0_attr_grp,
+ &sock1_attr_grp,
+ &sock2_attr_grp,
+ &sock3_attr_grp,
+ &sock4_attr_grp,
+ &sock5_attr_grp,
+ &sock6_attr_grp,
+ &sock7_attr_grp,
+ NULL
+};
+
+static inline bool is_f1a_m0h(void)
+{
+ if (boot_cpu_data.x86 == 0x1A && boot_cpu_data.x86_model <= 0x0F)
+ return true;
+
+ return false;
+}
+
+static int init_platform_device(struct device *dev)
+{
+ struct hsmp_socket *sock;
+ int ret, i;
+
+ for (i = 0; i < hsmp_pdev->num_sockets; i++) {
+ sock = &hsmp_pdev->sock[i];
+ sock->sock_ind = i;
+ sock->dev = dev;
+ sock->mbinfo.base_addr = SMN_HSMP_BASE;
+ sock->amd_hsmp_rdwr = amd_hsmp_pci_rdwr;
+
+ /*
+ * This is a transitional change from non-ACPI to ACPI, only
+ * family 0x1A, model 0x00 platform is supported for both ACPI and non-ACPI.
+ */
+ if (is_f1a_m0h())
+ sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID_F1A_M0H;
+ else
+ sock->mbinfo.msg_id_off = SMN_HSMP_MSG_ID;
+
+ sock->mbinfo.msg_resp_off = SMN_HSMP_MSG_RESP;
+ sock->mbinfo.msg_arg_off = SMN_HSMP_MSG_DATA;
+ sema_init(&sock->hsmp_sem, 1);
+
+ /* Test the hsmp interface on each socket */
+ ret = hsmp_test(i, 0xDEADBEEF);
+ if (ret) {
+ dev_err(dev, "HSMP test message failed on Fam:%x model:%x\n",
+ boot_cpu_data.x86, boot_cpu_data.x86_model);
+ dev_err(dev, "Is HSMP disabled in BIOS ?\n");
+ return ret;
+ }
+
+ ret = hsmp_cache_proto_ver(i);
+ if (ret) {
+ dev_err(dev, "Failed to read HSMP protocol version\n");
+ return ret;
+ }
+
+ if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) {
+ ret = hsmp_get_tbl_dram_base(i);
+ 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;
+}
+
+static int hsmp_pltdrv_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets,
+ sizeof(*hsmp_pdev->sock),
+ GFP_KERNEL);
+ if (!hsmp_pdev->sock)
+ return -ENOMEM;
+
+ ret = init_platform_device(&pdev->dev);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to init HSMP mailbox\n");
+ return ret;
+ }
+
+ return hsmp_misc_register(&pdev->dev);
+}
+
+static void hsmp_pltdrv_remove(struct platform_device *pdev)
+{
+ hsmp_misc_deregister();
+}
+
+static struct platform_driver amd_hsmp_driver = {
+ .probe = hsmp_pltdrv_probe,
+ .remove = hsmp_pltdrv_remove,
+ .driver = {
+ .name = DRIVER_NAME,
+ .dev_groups = hsmp_groups,
+ },
+};
+
+static struct platform_device *amd_hsmp_platdev;
+
+static int hsmp_plat_dev_register(void)
+{
+ int ret;
+
+ amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, PLATFORM_DEVID_NONE);
+ if (!amd_hsmp_platdev)
+ return -ENOMEM;
+
+ ret = platform_device_add(amd_hsmp_platdev);
+ if (ret)
+ platform_device_put(amd_hsmp_platdev);
+
+ return ret;
+}
+
+/*
+ * This check is only needed for backward compatibility of previous platforms.
+ * All new platforms are expected to support ACPI based probing.
+ */
+static bool legacy_hsmp_support(void)
+{
+ if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD)
+ return false;
+
+ switch (boot_cpu_data.x86) {
+ case 0x19:
+ switch (boot_cpu_data.x86_model) {
+ case 0x00 ... 0x1F:
+ case 0x30 ... 0x3F:
+ case 0x90 ... 0x9F:
+ case 0xA0 ... 0xAF:
+ return true;
+ default:
+ return false;
+ }
+ case 0x1A:
+ switch (boot_cpu_data.x86_model) {
+ case 0x00 ... 0x0F:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return false;
+ }
+
+ return false;
+}
+
+static int __init hsmp_plt_init(void)
+{
+ int ret = -ENODEV;
+
+ if (!legacy_hsmp_support()) {
+ pr_info("HSMP is not supported on Family:%x model:%x\n",
+ boot_cpu_data.x86, boot_cpu_data.x86_model);
+ return ret;
+ }
+
+ if (acpi_dev_present(ACPI_HSMP_DEVICE_HID, NULL, -1))
+ return -ENODEV;
+
+ hsmp_pdev = get_hsmp_pdev();
+ if (!hsmp_pdev)
+ return -ENOMEM;
+
+ /*
+ * amd_num_nodes() returns number of SMN/DF interfaces present in the system
+ * if we have N SMN/DF interfaces that ideally means N sockets
+ */
+ hsmp_pdev->num_sockets = amd_num_nodes();
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_NUM_NODES)
+ return ret;
+
+ ret = platform_driver_register(&amd_hsmp_driver);
+ if (ret)
+ return ret;
+
+ ret = hsmp_plat_dev_register();
+ if (ret)
+ platform_driver_unregister(&amd_hsmp_driver);
+
+ return ret;
+}
+
+static void __exit hsmp_plt_exit(void)
+{
+ platform_device_unregister(amd_hsmp_platdev);
+ platform_driver_unregister(&amd_hsmp_driver);
+}
+
+device_initcall(hsmp_plt_init);
+module_exit(hsmp_plt_exit);
+
+MODULE_IMPORT_NS("AMD_HSMP");
+MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver");
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/amd/pmc/Kconfig b/drivers/platform/x86/amd/pmc/Kconfig
index 883c0a95ac0c..eeffdafd686e 100644
--- a/drivers/platform/x86/amd/pmc/Kconfig
+++ b/drivers/platform/x86/amd/pmc/Kconfig
@@ -5,7 +5,7 @@
config AMD_PMC
tristate "AMD SoC PMC driver"
- depends on ACPI && PCI && RTC_CLASS && AMD_NB
+ depends on ACPI && PCI && RTC_CLASS && AMD_NODE
depends on SUSPEND
select SERIO
help
@@ -18,3 +18,18 @@ config AMD_PMC
If you choose to compile this driver as a module the module will be
called amd-pmc.
+
+config AMD_MP2_STB
+ bool "AMD SoC MP2 STB function"
+ depends on AMD_PMC
+ default AMD_PMC
+ help
+ AMD MP2 STB function provides a data buffer used to log debug
+ information about the system execution during S2Idle suspend/resume.
+ A data buffer known as the STB (Smart Trace Buffer) is a circular
+ buffer which is a low-level log for the SoC which is used to debug
+ any hangs/stalls during S2Idle suspend/resume.
+
+ Creates debugfs to get STB, a userspace daemon can access STB log of
+ last S2Idle suspend/resume which can help to debug if hangs/stalls
+ during S2Idle suspend/resume.
diff --git a/drivers/platform/x86/amd/pmc/Makefile b/drivers/platform/x86/amd/pmc/Makefile
index 4aaa29d351c9..bb6905c4cae9 100644
--- a/drivers/platform/x86/amd/pmc/Makefile
+++ b/drivers/platform/x86/amd/pmc/Makefile
@@ -4,5 +4,6 @@
# AMD Power Management Controller Driver
#
-amd-pmc-objs := pmc.o pmc-quirks.o
-obj-$(CONFIG_AMD_PMC) += amd-pmc.o
+obj-$(CONFIG_AMD_PMC) += amd-pmc.o
+amd-pmc-y := pmc.o pmc-quirks.o mp1_stb.o
+amd-pmc-$(CONFIG_AMD_MP2_STB) += mp2_stb.o
diff --git a/drivers/platform/x86/amd/pmc/mp1_stb.c b/drivers/platform/x86/amd/pmc/mp1_stb.c
new file mode 100644
index 000000000000..3b9b9f30faa3
--- /dev/null
+++ b/drivers/platform/x86/amd/pmc/mp1_stb.c
@@ -0,0 +1,332 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD MP1 Smart Trace Buffer (STB) Layer
+ *
+ * Copyright (c) 2024, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ * Sanket Goswami <Sanket.Goswami@amd.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <asm/amd/nb.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+
+#include "pmc.h"
+
+/* STB Spill to DRAM Parameters */
+#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
+#define S2D_TELEMETRY_BYTES_MAX 0x100000U
+#define S2D_RSVD_RAM_SPACE 0x100000
+
+/* STB Registers */
+#define AMD_STB_PMI_0 0x03E30600
+#define AMD_PMC_STB_DUMMY_PC 0xC6000007
+
+/* STB Spill to DRAM Message Definition */
+#define STB_FORCE_FLUSH_DATA 0xCF
+#define FIFO_SIZE 4096
+
+/* STB S2D(Spill to DRAM) has different message port offset */
+#define AMD_S2D_REGISTER_MESSAGE 0xA20
+#define AMD_S2D_REGISTER_RESPONSE 0xA80
+#define AMD_S2D_REGISTER_ARGUMENT 0xA88
+
+/* STB S2D (Spill to DRAM) message port offset for 44h model */
+#define AMD_GNR_REGISTER_MESSAGE 0x524
+#define AMD_GNR_REGISTER_RESPONSE 0x570
+#define AMD_GNR_REGISTER_ARGUMENT 0xA40
+
+static bool enable_stb;
+module_param(enable_stb, bool, 0644);
+MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism");
+
+static bool dump_custom_stb;
+module_param(dump_custom_stb, bool, 0644);
+MODULE_PARM_DESC(dump_custom_stb, "Enable to dump full STB buffer");
+
+enum s2d_arg {
+ S2D_TELEMETRY_SIZE = 0x01,
+ S2D_PHYS_ADDR_LOW,
+ S2D_PHYS_ADDR_HIGH,
+ S2D_NUM_SAMPLES,
+ S2D_DRAM_SIZE,
+};
+
+struct amd_stb_v2_data {
+ size_t size;
+ u8 data[] __counted_by(size);
+};
+
+int amd_stb_write(struct amd_pmc_dev *dev, u32 data)
+{
+ int err;
+
+ err = amd_smn_write(0, AMD_STB_PMI_0, data);
+ if (err) {
+ dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_STB_PMI_0);
+ return pcibios_err_to_errno(err);
+ }
+
+ return 0;
+}
+
+int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf)
+{
+ int i, err;
+
+ for (i = 0; i < FIFO_SIZE; i++) {
+ err = amd_smn_read(0, AMD_STB_PMI_0, buf++);
+ if (err) {
+ dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_STB_PMI_0);
+ return pcibios_err_to_errno(err);
+ }
+ }
+
+ return 0;
+}
+
+static int amd_stb_debugfs_open(struct inode *inode, struct file *filp)
+{
+ struct amd_pmc_dev *dev = filp->f_inode->i_private;
+ u32 size = FIFO_SIZE * sizeof(u32);
+ u32 *buf;
+ int rc;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ rc = amd_stb_read(dev, buf);
+ if (rc) {
+ kfree(buf);
+ return rc;
+ }
+
+ filp->private_data = buf;
+ return rc;
+}
+
+static ssize_t amd_stb_debugfs_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
+{
+ if (!filp->private_data)
+ return -EINVAL;
+
+ return simple_read_from_buffer(buf, size, pos, filp->private_data,
+ FIFO_SIZE * sizeof(u32));
+}
+
+static int amd_stb_debugfs_release(struct inode *inode, struct file *filp)
+{
+ kfree(filp->private_data);
+ return 0;
+}
+
+static const struct file_operations amd_stb_debugfs_fops = {
+ .owner = THIS_MODULE,
+ .open = amd_stb_debugfs_open,
+ .read = amd_stb_debugfs_read,
+ .release = amd_stb_debugfs_release,
+};
+
+/* Enhanced STB Firmware Reporting Mechanism */
+static int amd_stb_handle_efr(struct file *filp)
+{
+ struct amd_pmc_dev *dev = filp->f_inode->i_private;
+ struct amd_stb_v2_data *stb_data_arr;
+ u32 fsize;
+
+ fsize = dev->dram_size - S2D_RSVD_RAM_SPACE;
+ stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
+ if (!stb_data_arr)
+ return -ENOMEM;
+
+ stb_data_arr->size = fsize;
+ memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
+ filp->private_data = stb_data_arr;
+
+ return 0;
+}
+
+static int amd_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
+{
+ struct amd_pmc_dev *dev = filp->f_inode->i_private;
+ u32 fsize, num_samples, val, stb_rdptr_offset = 0;
+ struct amd_stb_v2_data *stb_data_arr;
+ int ret;
+
+ /* Write dummy postcode while reading the STB buffer */
+ ret = amd_stb_write(dev, AMD_PMC_STB_DUMMY_PC);
+ if (ret)
+ dev_err(dev->dev, "error writing to STB: %d\n", ret);
+
+ /* Spill to DRAM num_samples uses separate SMU message port */
+ dev->msg_port = MSG_PORT_S2D;
+
+ ret = amd_pmc_send_cmd(dev, 0, &val, STB_FORCE_FLUSH_DATA, 1);
+ if (ret)
+ dev_dbg_once(dev->dev, "S2D force flush not supported: %d\n", ret);
+
+ /*
+ * We have a custom stb size and the PMFW is supposed to give
+ * the enhanced dram size. Note that we land here only for the
+ * platforms that support enhanced dram size reporting.
+ */
+ if (dump_custom_stb)
+ return amd_stb_handle_efr(filp);
+
+ /* Get the num_samples to calculate the last push location */
+ ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->stb_arg.s2d_msg_id, true);
+ /* Clear msg_port for other SMU operation */
+ dev->msg_port = MSG_PORT_PMC;
+ if (ret) {
+ dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
+ return ret;
+ }
+
+ fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
+ stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
+ if (!stb_data_arr)
+ return -ENOMEM;
+
+ stb_data_arr->size = fsize;
+
+ /*
+ * Start capturing data from the last push location.
+ * This is for general cases, where the stb limits
+ * are meant for standard usage.
+ */
+ if (num_samples > S2D_TELEMETRY_BYTES_MAX) {
+ /* First read oldest data starting 1 behind last write till end of ringbuffer */
+ stb_rdptr_offset = num_samples % S2D_TELEMETRY_BYTES_MAX;
+ fsize = S2D_TELEMETRY_BYTES_MAX - stb_rdptr_offset;
+
+ memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr + stb_rdptr_offset, fsize);
+ /* Second copy the newer samples from offset 0 - last write */
+ memcpy_fromio(stb_data_arr->data + fsize, dev->stb_virt_addr, stb_rdptr_offset);
+ } else {
+ memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
+ }
+
+ filp->private_data = stb_data_arr;
+
+ return 0;
+}
+
+static ssize_t amd_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
+ loff_t *pos)
+{
+ struct amd_stb_v2_data *data = filp->private_data;
+
+ return simple_read_from_buffer(buf, size, pos, data->data, data->size);
+}
+
+static int amd_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
+{
+ kfree(filp->private_data);
+ return 0;
+}
+
+static const struct file_operations amd_stb_debugfs_fops_v2 = {
+ .owner = THIS_MODULE,
+ .open = amd_stb_debugfs_open_v2,
+ .read = amd_stb_debugfs_read_v2,
+ .release = amd_stb_debugfs_release_v2,
+};
+
+static void amd_stb_update_args(struct amd_pmc_dev *dev)
+{
+ if (cpu_feature_enabled(X86_FEATURE_ZEN5))
+ switch (boot_cpu_data.x86_model) {
+ case 0x44:
+ dev->stb_arg.msg = AMD_GNR_REGISTER_MESSAGE;
+ dev->stb_arg.arg = AMD_GNR_REGISTER_ARGUMENT;
+ dev->stb_arg.resp = AMD_GNR_REGISTER_RESPONSE;
+ return;
+ default:
+ break;
+ }
+
+ dev->stb_arg.msg = AMD_S2D_REGISTER_MESSAGE;
+ dev->stb_arg.arg = AMD_S2D_REGISTER_ARGUMENT;
+ dev->stb_arg.resp = AMD_S2D_REGISTER_RESPONSE;
+}
+
+static bool amd_is_stb_supported(struct amd_pmc_dev *dev)
+{
+ switch (dev->cpu_id) {
+ case AMD_CPU_ID_YC:
+ case AMD_CPU_ID_CB:
+ if (boot_cpu_data.x86_model == 0x44)
+ dev->stb_arg.s2d_msg_id = 0x9B;
+ else
+ dev->stb_arg.s2d_msg_id = 0xBE;
+ break;
+ case AMD_CPU_ID_PS:
+ dev->stb_arg.s2d_msg_id = 0x85;
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ if (boot_cpu_data.x86_model == 0x70)
+ dev->stb_arg.s2d_msg_id = 0xF1;
+ else
+ dev->stb_arg.s2d_msg_id = 0xDE;
+ break;
+ default:
+ return false;
+ }
+
+ amd_stb_update_args(dev);
+ return true;
+}
+
+int amd_stb_s2d_init(struct amd_pmc_dev *dev)
+{
+ u32 phys_addr_low, phys_addr_hi;
+ u64 stb_phys_addr;
+ u32 size = 0;
+ int ret;
+
+ if (!enable_stb)
+ return 0;
+
+ if (amd_is_stb_supported(dev)) {
+ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
+ &amd_stb_debugfs_fops_v2);
+ } else {
+ debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
+ &amd_stb_debugfs_fops);
+ return 0;
+ }
+
+ /* Spill to DRAM feature uses separate SMU message port */
+ dev->msg_port = MSG_PORT_S2D;
+
+ amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->stb_arg.s2d_msg_id, true);
+ if (size != S2D_TELEMETRY_BYTES_MAX)
+ return -EIO;
+
+ /* Get DRAM size */
+ ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->stb_arg.s2d_msg_id, true);
+ if (ret || !dev->dram_size)
+ dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
+
+ /* Get STB DRAM address */
+ amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->stb_arg.s2d_msg_id, true);
+ amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->stb_arg.s2d_msg_id, true);
+
+ stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
+
+ /* Clear msg_port for other SMU operation */
+ dev->msg_port = MSG_PORT_PMC;
+
+ dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
+ if (!dev->stb_virt_addr)
+ return -ENOMEM;
+
+ return 0;
+}
diff --git a/drivers/platform/x86/amd/pmc/mp2_stb.c b/drivers/platform/x86/amd/pmc/mp2_stb.c
new file mode 100644
index 000000000000..9775ddc1b27a
--- /dev/null
+++ b/drivers/platform/x86/amd/pmc/mp2_stb.c
@@ -0,0 +1,280 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * AMD MP2 STB layer
+ *
+ * Copyright (c) 2024, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
+ */
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/io-64-nonatomic-lo-hi.h>
+#include <linux/iopoll.h>
+#include <linux/pci.h>
+#include <linux/sizes.h>
+#include <linux/time.h>
+
+#include "pmc.h"
+
+#define VALID_MSG 0xA
+#define VALID_RESPONSE 2
+
+#define AMD_C2P_MSG0 0x10500
+#define AMD_C2P_MSG1 0x10504
+#define AMD_P2C_MSG0 0x10680
+#define AMD_P2C_MSG1 0x10684
+
+#define MP2_RESP_SLEEP_US 500
+#define MP2_RESP_TIMEOUT_US (1600 * USEC_PER_MSEC)
+
+#define MP2_STB_DATA_LEN_2KB 1
+#define MP2_STB_DATA_LEN_16KB 4
+
+#define MP2_MMIO_BAR 2
+
+struct mp2_cmd_base {
+ union {
+ u32 ul;
+ struct {
+ u32 cmd_id : 4;
+ u32 intr_disable : 1;
+ u32 is_dma_used : 1;
+ u32 rsvd : 26;
+ } field;
+ };
+};
+
+struct mp2_cmd_response {
+ union {
+ u32 resp;
+ struct {
+ u32 cmd_id : 4;
+ u32 status : 4;
+ u32 response : 4;
+ u32 rsvd2 : 20;
+ } field;
+ };
+};
+
+struct mp2_stb_data_valid {
+ union {
+ u32 data_valid;
+ struct {
+ u32 valid : 16;
+ u32 length : 16;
+ } val;
+ };
+};
+
+static int amd_mp2_wait_response(struct amd_mp2_dev *mp2, u8 cmd_id, u32 command_sts)
+{
+ struct mp2_cmd_response cmd_resp;
+
+ if (!readl_poll_timeout(mp2->mmio + AMD_P2C_MSG0, cmd_resp.resp,
+ (cmd_resp.field.response == 0x0 &&
+ cmd_resp.field.status == command_sts &&
+ cmd_resp.field.cmd_id == cmd_id), MP2_RESP_SLEEP_US,
+ MP2_RESP_TIMEOUT_US))
+ return cmd_resp.field.status;
+
+ return -ETIMEDOUT;
+}
+
+static void amd_mp2_stb_send_cmd(struct amd_mp2_dev *mp2, u8 cmd_id, bool is_dma_used)
+{
+ struct mp2_cmd_base cmd_base;
+
+ cmd_base.ul = 0;
+ cmd_base.field.cmd_id = cmd_id;
+ cmd_base.field.intr_disable = 1;
+ cmd_base.field.is_dma_used = is_dma_used;
+
+ writeq(mp2->dma_addr, mp2->mmio + AMD_C2P_MSG1);
+ writel(cmd_base.ul, mp2->mmio + AMD_C2P_MSG0);
+}
+
+static int amd_mp2_stb_region(struct amd_mp2_dev *mp2)
+{
+ struct device *dev = &mp2->pdev->dev;
+ unsigned int len = mp2->stb_len;
+
+ if (!mp2->stbdata) {
+ mp2->vslbase = dmam_alloc_coherent(dev, len, &mp2->dma_addr, GFP_KERNEL);
+ if (!mp2->vslbase)
+ return -ENOMEM;
+
+ mp2->stbdata = devm_kzalloc(dev, len, GFP_KERNEL);
+ if (!mp2->stbdata)
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static int amd_mp2_process_cmd(struct amd_mp2_dev *mp2, struct file *filp)
+{
+ struct device *dev = &mp2->pdev->dev;
+ struct mp2_stb_data_valid stb_dv;
+ int status;
+
+ stb_dv.data_valid = readl(mp2->mmio + AMD_P2C_MSG1);
+
+ if (stb_dv.val.valid != VALID_MSG) {
+ dev_dbg(dev, "Invalid STB data\n");
+ return -EBADMSG;
+ }
+
+ if (stb_dv.val.length != MP2_STB_DATA_LEN_2KB &&
+ stb_dv.val.length != MP2_STB_DATA_LEN_16KB) {
+ dev_dbg(dev, "Unsupported length\n");
+ return -EMSGSIZE;
+ }
+
+ mp2->stb_len = BIT(stb_dv.val.length) * SZ_1K;
+
+ status = amd_mp2_stb_region(mp2);
+ if (status) {
+ dev_err(dev, "Failed to init STB region, status %d\n", status);
+ return status;
+ }
+
+ amd_mp2_stb_send_cmd(mp2, VALID_MSG, true);
+ status = amd_mp2_wait_response(mp2, VALID_MSG, VALID_RESPONSE);
+ if (status == VALID_RESPONSE) {
+ memcpy_fromio(mp2->stbdata, mp2->vslbase, mp2->stb_len);
+ filp->private_data = mp2->stbdata;
+ mp2->is_stb_data = true;
+ } else {
+ dev_err(dev, "Failed to start STB dump, status %d\n", status);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int amd_mp2_stb_debugfs_open(struct inode *inode, struct file *filp)
+{
+ struct amd_pmc_dev *dev = filp->f_inode->i_private;
+ struct amd_mp2_dev *mp2 = dev->mp2;
+
+ if (mp2) {
+ if (!mp2->is_stb_data)
+ return amd_mp2_process_cmd(mp2, filp);
+
+ filp->private_data = mp2->stbdata;
+
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+static ssize_t amd_mp2_stb_debugfs_read(struct file *filp, char __user *buf, size_t size,
+ loff_t *pos)
+{
+ struct amd_pmc_dev *dev = filp->f_inode->i_private;
+ struct amd_mp2_dev *mp2 = dev->mp2;
+
+ if (!mp2)
+ return -ENODEV;
+
+ if (!filp->private_data)
+ return -EINVAL;
+
+ return simple_read_from_buffer(buf, size, pos, filp->private_data, mp2->stb_len);
+}
+
+static const struct file_operations amd_mp2_stb_debugfs_fops = {
+ .owner = THIS_MODULE,
+ .open = amd_mp2_stb_debugfs_open,
+ .read = amd_mp2_stb_debugfs_read,
+};
+
+static void amd_mp2_dbgfs_register(struct amd_pmc_dev *dev)
+{
+ if (!dev->dbgfs_dir)
+ return;
+
+ debugfs_create_file("stb_read_previous_boot", 0644, dev->dbgfs_dir, dev,
+ &amd_mp2_stb_debugfs_fops);
+}
+
+void amd_mp2_stb_deinit(struct amd_pmc_dev *dev)
+{
+ struct amd_mp2_dev *mp2 = dev->mp2;
+ struct pci_dev *pdev;
+
+ if (mp2 && mp2->pdev) {
+ pdev = mp2->pdev;
+
+ if (mp2->mmio)
+ pci_clear_master(pdev);
+
+ pci_dev_put(pdev);
+
+ if (mp2->devres_gid)
+ devres_release_group(&pdev->dev, mp2->devres_gid);
+
+ dev->mp2 = NULL;
+ }
+}
+
+void amd_mp2_stb_init(struct amd_pmc_dev *dev)
+{
+ struct amd_mp2_dev *mp2 = NULL;
+ struct pci_dev *pdev;
+ int rc;
+
+ mp2 = devm_kzalloc(dev->dev, sizeof(*mp2), GFP_KERNEL);
+ if (!mp2)
+ return;
+
+ pdev = pci_get_device(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_MP2_STB, NULL);
+ if (!pdev)
+ return;
+
+ dev->mp2 = mp2;
+ mp2->pdev = pdev;
+
+ mp2->devres_gid = devres_open_group(&pdev->dev, NULL, GFP_KERNEL);
+ if (!mp2->devres_gid) {
+ dev_err(&pdev->dev, "devres_open_group failed\n");
+ goto mp2_error;
+ }
+
+ rc = pcim_enable_device(pdev);
+ if (rc) {
+ dev_err(&pdev->dev, "pcim_enable_device failed\n");
+ goto mp2_error;
+ }
+
+ rc = pcim_iomap_regions(pdev, BIT(MP2_MMIO_BAR), "mp2 stb");
+ if (rc) {
+ dev_err(&pdev->dev, "pcim_iomap_regions failed\n");
+ goto mp2_error;
+ }
+
+ mp2->mmio = pcim_iomap_table(pdev)[MP2_MMIO_BAR];
+ if (!mp2->mmio) {
+ dev_err(&pdev->dev, "pcim_iomap_table failed\n");
+ goto mp2_error;
+ }
+
+ pci_set_master(pdev);
+
+ rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+ if (rc) {
+ dev_err(&pdev->dev, "failed to set DMA mask\n");
+ goto mp2_error;
+ }
+
+ amd_mp2_dbgfs_register(dev);
+
+ return;
+
+mp2_error:
+ amd_mp2_stb_deinit(dev);
+}
diff --git a/drivers/platform/x86/amd/pmc/pmc-quirks.c b/drivers/platform/x86/amd/pmc/pmc-quirks.c
index b4f49720c87f..5c7c01f66cde 100644
--- a/drivers/platform/x86/amd/pmc/pmc-quirks.c
+++ b/drivers/platform/x86/amd/pmc/pmc-quirks.c
@@ -11,6 +11,7 @@
#include <linux/dmi.h>
#include <linux/io.h>
#include <linux/ioport.h>
+#include <asm/amd/fch.h>
#include "pmc.h"
@@ -20,7 +21,7 @@ struct quirk_entry {
};
static struct quirk_entry quirk_s2idle_bug = {
- .s2idle_bug_mmio = 0xfed80380,
+ .s2idle_bug_mmio = FCH_PM_BASE + FCH_PM_SCRATCH,
};
static struct quirk_entry quirk_spurious_8042 = {
@@ -217,6 +218,13 @@ static const struct dmi_system_id fwbug_list[] = {
DMI_MATCH(DMI_BIOS_VERSION, "03.05"),
}
},
+ {
+ .ident = "MECHREVO Wujie 14X (GX4HRXL)",
+ .driver_data = &quirk_spurious_8042,
+ .matches = {
+ DMI_MATCH(DMI_BOARD_NAME, "WUJIE14-GX4HRXL"),
+ }
+ },
{}
};
diff --git a/drivers/platform/x86/amd/pmc/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index 108e12fd580f..37c7a57afee5 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -10,8 +10,8 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-#include <asm/amd_nb.h>
#include <linux/acpi.h>
+#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/debugfs.h>
@@ -28,99 +28,36 @@
#include <linux/seq_file.h>
#include <linux/uaccess.h>
-#include "pmc.h"
-
-/* SMU communication registers */
-#define AMD_PMC_REGISTER_RESPONSE 0x980
-#define AMD_PMC_REGISTER_ARGUMENT 0x9BC
-
-/* PMC Scratch Registers */
-#define AMD_PMC_SCRATCH_REG_CZN 0x94
-#define AMD_PMC_SCRATCH_REG_YC 0xD14
-#define AMD_PMC_SCRATCH_REG_1AH 0xF14
-
-/* STB Registers */
-#define AMD_PMC_STB_PMI_0 0x03E30600
-#define AMD_PMC_STB_S2IDLE_PREPARE 0xC6000001
-#define AMD_PMC_STB_S2IDLE_RESTORE 0xC6000002
-#define AMD_PMC_STB_S2IDLE_CHECK 0xC6000003
-#define AMD_PMC_STB_DUMMY_PC 0xC6000007
-
-/* STB S2D(Spill to DRAM) has different message port offset */
-#define AMD_S2D_REGISTER_MESSAGE 0xA20
-#define AMD_S2D_REGISTER_RESPONSE 0xA80
-#define AMD_S2D_REGISTER_ARGUMENT 0xA88
-
-/* STB Spill to DRAM Parameters */
-#define S2D_TELEMETRY_BYTES_MAX 0x100000U
-#define S2D_RSVD_RAM_SPACE 0x100000
-#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000
-
-/* STB Spill to DRAM Message Definition */
-#define STB_FORCE_FLUSH_DATA 0xCF
-
-/* Base address of SMU for mapping physical address to virtual address */
-#define AMD_PMC_MAPPING_SIZE 0x01000
-#define AMD_PMC_BASE_ADDR_OFFSET 0x10000
-#define AMD_PMC_BASE_ADDR_LO 0x13B102E8
-#define AMD_PMC_BASE_ADDR_HI 0x13B102EC
-#define AMD_PMC_BASE_ADDR_LO_MASK GENMASK(15, 0)
-#define AMD_PMC_BASE_ADDR_HI_MASK GENMASK(31, 20)
-
-/* SMU Response Codes */
-#define AMD_PMC_RESULT_OK 0x01
-#define AMD_PMC_RESULT_CMD_REJECT_BUSY 0xFC
-#define AMD_PMC_RESULT_CMD_REJECT_PREREQ 0xFD
-#define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE
-#define AMD_PMC_RESULT_FAILED 0xFF
-
-/* FCH SSC Registers */
-#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30
-#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34
-#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38
-#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C
-#define FCH_SSC_MAPPING_SIZE 0x800
-#define FCH_BASE_PHY_ADDR_LOW 0xFED81100
-#define FCH_BASE_PHY_ADDR_HIGH 0x00000000
-
-/* SMU Message Definations */
-#define SMU_MSG_GETSMUVERSION 0x02
-#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04
-#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05
-#define SMU_MSG_LOG_START 0x06
-#define SMU_MSG_LOG_RESET 0x07
-#define SMU_MSG_LOG_DUMP_DATA 0x08
-#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09
-
-#define PMC_MSG_DELAY_MIN_US 50
-#define RESPONSE_REGISTER_LOOP_MAX 20000
-
-#define DELAY_MIN_US 2000
-#define DELAY_MAX_US 3000
-#define FIFO_SIZE 4096
-
-enum amd_pmc_def {
- MSG_TEST = 0x01,
- MSG_OS_HINT_PCO,
- MSG_OS_HINT_RN,
-};
-
-enum s2d_arg {
- S2D_TELEMETRY_SIZE = 0x01,
- S2D_PHYS_ADDR_LOW,
- S2D_PHYS_ADDR_HIGH,
- S2D_NUM_SAMPLES,
- S2D_DRAM_SIZE,
-};
+#include <asm/amd/node.h>
-struct amd_pmc_stb_v2_data {
- size_t size;
- u8 data[] __counted_by(size);
-};
+#include "pmc.h"
-struct amd_pmc_bit_map {
- const char *name;
- u32 bit_mask;
+static const struct amd_pmc_bit_map soc15_ip_blk_v2[] = {
+ {"DISPLAY", BIT(0)},
+ {"CPU", BIT(1)},
+ {"GFX", BIT(2)},
+ {"VDD", BIT(3)},
+ {"VDD_CCX", BIT(4)},
+ {"ACP", BIT(5)},
+ {"VCN_0", BIT(6)},
+ {"VCN_1", BIT(7)},
+ {"ISP", BIT(8)},
+ {"NBIO", BIT(9)},
+ {"DF", BIT(10)},
+ {"USB3_0", BIT(11)},
+ {"USB3_1", BIT(12)},
+ {"LAPIC", BIT(13)},
+ {"USB3_2", BIT(14)},
+ {"USB4_RT0", BIT(15)},
+ {"USB4_RT1", BIT(16)},
+ {"USB4_0", BIT(17)},
+ {"USB4_1", BIT(18)},
+ {"MPM", BIT(19)},
+ {"JPEG_0", BIT(20)},
+ {"JPEG_1", BIT(21)},
+ {"IPU", BIT(22)},
+ {"UMSCH", BIT(23)},
+ {"VPE", BIT(24)},
};
static const struct amd_pmc_bit_map soc15_ip_blk[] = {
@@ -146,25 +83,13 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = {
{"IPU", BIT(19)},
{"UMSCH", BIT(20)},
{"VPE", BIT(21)},
- {}
};
-static bool enable_stb;
-module_param(enable_stb, bool, 0644);
-MODULE_PARM_DESC(enable_stb, "Enable the STB debug mechanism");
-
static bool disable_workarounds;
module_param(disable_workarounds, bool, 0644);
MODULE_PARM_DESC(disable_workarounds, "Disable workarounds for platform bugs");
-static bool dump_custom_stb;
-module_param(dump_custom_stb, bool, 0644);
-MODULE_PARM_DESC(dump_custom_stb, "Enable to dump full STB buffer");
-
static struct amd_pmc_dev pmc;
-static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret);
-static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf);
-static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data);
static inline u32 amd_pmc_reg_read(struct amd_pmc_dev *dev, int reg_offset)
{
@@ -176,172 +101,6 @@ static inline void amd_pmc_reg_write(struct amd_pmc_dev *dev, int reg_offset, u3
iowrite32(val, dev->regbase + reg_offset);
}
-struct smu_metrics {
- u32 table_version;
- u32 hint_count;
- u32 s0i3_last_entry_status;
- u32 timein_s0i2;
- u64 timeentering_s0i3_lastcapture;
- u64 timeentering_s0i3_totaltime;
- u64 timeto_resume_to_os_lastcapture;
- u64 timeto_resume_to_os_totaltime;
- u64 timein_s0i3_lastcapture;
- u64 timein_s0i3_totaltime;
- u64 timein_swdrips_lastcapture;
- u64 timein_swdrips_totaltime;
- u64 timecondition_notmet_lastcapture[32];
- u64 timecondition_notmet_totaltime[32];
-} __packed;
-
-static int amd_pmc_stb_debugfs_open(struct inode *inode, struct file *filp)
-{
- struct amd_pmc_dev *dev = filp->f_inode->i_private;
- u32 size = FIFO_SIZE * sizeof(u32);
- u32 *buf;
- int rc;
-
- buf = kzalloc(size, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- rc = amd_pmc_read_stb(dev, buf);
- if (rc) {
- kfree(buf);
- return rc;
- }
-
- filp->private_data = buf;
- return rc;
-}
-
-static ssize_t amd_pmc_stb_debugfs_read(struct file *filp, char __user *buf, size_t size,
- loff_t *pos)
-{
- if (!filp->private_data)
- return -EINVAL;
-
- return simple_read_from_buffer(buf, size, pos, filp->private_data,
- FIFO_SIZE * sizeof(u32));
-}
-
-static int amd_pmc_stb_debugfs_release(struct inode *inode, struct file *filp)
-{
- kfree(filp->private_data);
- return 0;
-}
-
-static const struct file_operations amd_pmc_stb_debugfs_fops = {
- .owner = THIS_MODULE,
- .open = amd_pmc_stb_debugfs_open,
- .read = amd_pmc_stb_debugfs_read,
- .release = amd_pmc_stb_debugfs_release,
-};
-
-/* Enhanced STB Firmware Reporting Mechanism */
-static int amd_pmc_stb_handle_efr(struct file *filp)
-{
- struct amd_pmc_dev *dev = filp->f_inode->i_private;
- struct amd_pmc_stb_v2_data *stb_data_arr;
- u32 fsize;
-
- fsize = dev->dram_size - S2D_RSVD_RAM_SPACE;
- stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
- if (!stb_data_arr)
- return -ENOMEM;
-
- stb_data_arr->size = fsize;
- memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
- filp->private_data = stb_data_arr;
-
- return 0;
-}
-
-static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp)
-{
- struct amd_pmc_dev *dev = filp->f_inode->i_private;
- u32 fsize, num_samples, val, stb_rdptr_offset = 0;
- struct amd_pmc_stb_v2_data *stb_data_arr;
- int ret;
-
- /* Write dummy postcode while reading the STB buffer */
- ret = amd_pmc_write_stb(dev, AMD_PMC_STB_DUMMY_PC);
- if (ret)
- dev_err(dev->dev, "error writing to STB: %d\n", ret);
-
- /* Spill to DRAM num_samples uses separate SMU message port */
- dev->msg_port = 1;
-
- ret = amd_pmc_send_cmd(dev, 0, &val, STB_FORCE_FLUSH_DATA, 1);
- if (ret)
- dev_dbg_once(dev->dev, "S2D force flush not supported: %d\n", ret);
-
- /*
- * We have a custom stb size and the PMFW is supposed to give
- * the enhanced dram size. Note that we land here only for the
- * platforms that support enhanced dram size reporting.
- */
- if (dump_custom_stb)
- return amd_pmc_stb_handle_efr(filp);
-
- /* Get the num_samples to calculate the last push location */
- ret = amd_pmc_send_cmd(dev, S2D_NUM_SAMPLES, &num_samples, dev->s2d_msg_id, true);
- /* Clear msg_port for other SMU operation */
- dev->msg_port = 0;
- if (ret) {
- dev_err(dev->dev, "error: S2D_NUM_SAMPLES not supported : %d\n", ret);
- return ret;
- }
-
- fsize = min(num_samples, S2D_TELEMETRY_BYTES_MAX);
- stb_data_arr = kmalloc(struct_size(stb_data_arr, data, fsize), GFP_KERNEL);
- if (!stb_data_arr)
- return -ENOMEM;
-
- stb_data_arr->size = fsize;
-
- /*
- * Start capturing data from the last push location.
- * This is for general cases, where the stb limits
- * are meant for standard usage.
- */
- if (num_samples > S2D_TELEMETRY_BYTES_MAX) {
- /* First read oldest data starting 1 behind last write till end of ringbuffer */
- stb_rdptr_offset = num_samples % S2D_TELEMETRY_BYTES_MAX;
- fsize = S2D_TELEMETRY_BYTES_MAX - stb_rdptr_offset;
-
- memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr + stb_rdptr_offset, fsize);
- /* Second copy the newer samples from offset 0 - last write */
- memcpy_fromio(stb_data_arr->data + fsize, dev->stb_virt_addr, stb_rdptr_offset);
- } else {
- memcpy_fromio(stb_data_arr->data, dev->stb_virt_addr, fsize);
- }
-
- filp->private_data = stb_data_arr;
-
- return 0;
-}
-
-static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size,
- loff_t *pos)
-{
- struct amd_pmc_stb_v2_data *data = filp->private_data;
-
- return simple_read_from_buffer(buf, size, pos, data->data, data->size);
-}
-
-static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp)
-{
- kfree(filp->private_data);
- return 0;
-}
-
-static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = {
- .owner = THIS_MODULE,
- .open = amd_pmc_stb_debugfs_open_v2,
- .read = amd_pmc_stb_debugfs_read_v2,
- .release = amd_pmc_stb_debugfs_release_v2,
-};
-
static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
{
switch (dev->cpu_id) {
@@ -350,17 +109,23 @@ static void amd_pmc_get_ip_info(struct amd_pmc_dev *dev)
case AMD_CPU_ID_YC:
case AMD_CPU_ID_CB:
dev->num_ips = 12;
- dev->s2d_msg_id = 0xBE;
+ dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case AMD_CPU_ID_PS:
dev->num_ips = 21;
- dev->s2d_msg_id = 0x85;
+ dev->ips_ptr = soc15_ip_blk;
dev->smu_msg = 0x538;
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- dev->num_ips = 22;
- dev->s2d_msg_id = 0xDE;
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ if (boot_cpu_data.x86_model == 0x70) {
+ dev->num_ips = ARRAY_SIZE(soc15_ip_blk_v2);
+ dev->ips_ptr = soc15_ip_blk_v2;
+ } else {
+ dev->num_ips = ARRAY_SIZE(soc15_ip_blk);
+ dev->ips_ptr = soc15_ip_blk;
+ }
dev->smu_msg = 0x938;
break;
}
@@ -401,11 +166,12 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev)
static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table)
{
- if (!pdev->smu_virt_addr) {
- int ret = amd_pmc_setup_smu_logging(pdev);
+ int rc;
- if (ret)
- return ret;
+ if (!pdev->smu_virt_addr) {
+ rc = amd_pmc_setup_smu_logging(pdev);
+ if (rc)
+ return rc;
}
if (pdev->cpu_id == AMD_CPU_ID_PCO)
@@ -454,10 +220,10 @@ static ssize_t smu_fw_version_show(struct device *d, struct device_attribute *at
char *buf)
{
struct amd_pmc_dev *dev = dev_get_drvdata(d);
+ int rc;
if (!dev->major) {
- int rc = amd_pmc_get_smu_version(dev);
-
+ rc = amd_pmc_get_smu_version(dev);
if (rc)
return rc;
}
@@ -468,10 +234,10 @@ static ssize_t smu_program_show(struct device *d, struct device_attribute *attr,
char *buf)
{
struct amd_pmc_dev *dev = dev_get_drvdata(d);
+ int rc;
if (!dev->major) {
- int rc = amd_pmc_get_smu_version(dev);
-
+ rc = amd_pmc_get_smu_version(dev);
if (rc)
return rc;
}
@@ -528,8 +294,8 @@ static int smu_fw_info_show(struct seq_file *s, void *unused)
seq_puts(s, "\n=== Active time (in us) ===\n");
for (idx = 0 ; idx < dev->num_ips ; idx++) {
- if (soc15_ip_blk[idx].bit_mask & dev->active_ips)
- seq_printf(s, "%-8s : %lld\n", soc15_ip_blk[idx].name,
+ if (dev->ips_ptr[idx].bit_mask & dev->active_ips)
+ seq_printf(s, "%-8s : %lld\n", dev->ips_ptr[idx].name,
table.timecondition_notmet_lastcapture[idx]);
}
@@ -597,6 +363,7 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev,
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_YC);
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
val = amd_pmc_reg_read(pdev, AMD_PMC_SCRATCH_REG_1AH);
break;
default:
@@ -623,19 +390,6 @@ static void amd_pmc_dbgfs_unregister(struct amd_pmc_dev *dev)
debugfs_remove_recursive(dev->dbgfs_dir);
}
-static bool amd_pmc_is_stb_supported(struct amd_pmc_dev *dev)
-{
- switch (dev->cpu_id) {
- case AMD_CPU_ID_YC:
- case AMD_CPU_ID_CB:
- case AMD_CPU_ID_PS:
- case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
- return true;
- default:
- return false;
- }
-}
-
static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
{
dev->dbgfs_dir = debugfs_create_dir("amd_pmc", NULL);
@@ -645,14 +399,17 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev)
&s0ix_stats_fops);
debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev,
&amd_pmc_idlemask_fops);
- /* Enable STB only when the module_param is set */
- if (enable_stb) {
- if (amd_pmc_is_stb_supported(dev))
- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
- &amd_pmc_stb_debugfs_fops_v2);
- else
- debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev,
- &amd_pmc_stb_debugfs_fops);
+}
+
+static char *amd_pmc_get_msg_port(struct amd_pmc_dev *dev)
+{
+ switch (dev->msg_port) {
+ case MSG_PORT_PMC:
+ return "PMC";
+ case MSG_PORT_S2D:
+ return "S2D";
+ default:
+ return "Invalid message port";
}
}
@@ -660,10 +417,10 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
{
u32 value, message, argument, response;
- if (dev->msg_port) {
- message = AMD_S2D_REGISTER_MESSAGE;
- argument = AMD_S2D_REGISTER_ARGUMENT;
- response = AMD_S2D_REGISTER_RESPONSE;
+ if (dev->msg_port == MSG_PORT_S2D) {
+ message = dev->stb_arg.msg;
+ argument = dev->stb_arg.arg;
+ response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
@@ -671,26 +428,26 @@ static void amd_pmc_dump_registers(struct amd_pmc_dev *dev)
}
value = amd_pmc_reg_read(dev, response);
- dev_dbg(dev->dev, "AMD_%s_REGISTER_RESPONSE:%x\n", dev->msg_port ? "S2D" : "PMC", value);
+ dev_dbg(dev->dev, "AMD_%s_REGISTER_RESPONSE:%x\n", amd_pmc_get_msg_port(dev), value);
value = amd_pmc_reg_read(dev, argument);
- dev_dbg(dev->dev, "AMD_%s_REGISTER_ARGUMENT:%x\n", dev->msg_port ? "S2D" : "PMC", value);
+ dev_dbg(dev->dev, "AMD_%s_REGISTER_ARGUMENT:%x\n", amd_pmc_get_msg_port(dev), value);
value = amd_pmc_reg_read(dev, message);
- dev_dbg(dev->dev, "AMD_%s_REGISTER_MESSAGE:%x\n", dev->msg_port ? "S2D" : "PMC", value);
+ dev_dbg(dev->dev, "AMD_%s_REGISTER_MESSAGE:%x\n", amd_pmc_get_msg_port(dev), value);
}
-static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret)
+int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret)
{
int rc;
u32 val, message, argument, response;
- mutex_lock(&dev->lock);
+ guard(mutex)(&dev->lock);
- if (dev->msg_port) {
- message = AMD_S2D_REGISTER_MESSAGE;
- argument = AMD_S2D_REGISTER_ARGUMENT;
- response = AMD_S2D_REGISTER_RESPONSE;
+ if (dev->msg_port == MSG_PORT_S2D) {
+ message = dev->stb_arg.msg;
+ argument = dev->stb_arg.arg;
+ response = dev->stb_arg.resp;
} else {
message = dev->smu_msg;
argument = AMD_PMC_REGISTER_ARGUMENT;
@@ -703,7 +460,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg,
PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "failed to talk to SMU\n");
- goto out_unlock;
+ return rc;
}
/* Write zero to response register */
@@ -721,7 +478,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg,
PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "SMU response timed out\n");
- goto out_unlock;
+ return rc;
}
switch (val) {
@@ -735,21 +492,19 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg,
case AMD_PMC_RESULT_CMD_REJECT_BUSY:
dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val);
rc = -EBUSY;
- goto out_unlock;
+ break;
case AMD_PMC_RESULT_CMD_UNKNOWN:
dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val);
rc = -EINVAL;
- goto out_unlock;
+ break;
case AMD_PMC_RESULT_CMD_REJECT_PREREQ:
case AMD_PMC_RESULT_FAILED:
default:
dev_err(dev->dev, "SMU cmd failed. err: 0x%x\n", val);
rc = -EIO;
- goto out_unlock;
+ break;
}
-out_unlock:
- mutex_unlock(&dev->lock);
amd_pmc_dump_registers(dev);
return rc;
}
@@ -764,6 +519,7 @@ static int amd_pmc_get_os_hint(struct amd_pmc_dev *dev)
case AMD_CPU_ID_CB:
case AMD_CPU_ID_PS:
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
return MSG_OS_HINT_RN;
}
return -EINVAL;
@@ -877,7 +633,7 @@ static void amd_pmc_s2idle_prepare(void)
return;
}
- rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_PREPARE);
+ rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_PREPARE);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
@@ -888,15 +644,14 @@ static void amd_pmc_s2idle_check(void)
struct smu_metrics table;
int rc;
- /* CZN: Ensure that future s0i3 entry attempts at least 10ms passed */
- if (pdev->cpu_id == AMD_CPU_ID_CZN && !get_metrics_table(pdev, &table) &&
- table.s0i3_last_entry_status)
- usleep_range(10000, 20000);
+ /* Avoid triggering OVP */
+ if (!get_metrics_table(pdev, &table) && table.s0i3_last_entry_status)
+ msleep(2500);
/* Dump the IdleMask before we add to the STB */
amd_pmc_idlemask_read(pdev, pdev->dev, NULL);
- rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_CHECK);
+ rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_CHECK);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
}
@@ -923,7 +678,7 @@ static void amd_pmc_s2idle_restore(void)
/* Let SMU know that we are looking for stats */
amd_pmc_dump_data(pdev);
- rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_S2IDLE_RESTORE);
+ rc = amd_stb_write(pdev, AMD_PMC_STB_S2IDLE_RESTORE);
if (rc)
dev_err(pdev->dev, "error writing to STB: %d\n", rc);
@@ -942,10 +697,14 @@ static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = {
static int amd_pmc_suspend_handler(struct device *dev)
{
struct amd_pmc_dev *pdev = dev_get_drvdata(dev);
+ int rc;
+ /*
+ * Must be called only from the same set of dev_pm_ops handlers
+ * as i8042_pm_suspend() is called: currently just from .suspend.
+ */
if (pdev->disable_8042_wakeup && !disable_workarounds) {
- int rc = amd_pmc_wa_irq1(pdev);
-
+ rc = amd_pmc_wa_irq1(pdev);
if (rc) {
dev_err(pdev->dev, "failed to adjust keyboard wakeup: %d\n", rc);
return rc;
@@ -955,7 +714,9 @@ static int amd_pmc_suspend_handler(struct device *dev)
return 0;
}
-static DEFINE_SIMPLE_DEV_PM_OPS(amd_pmc_pm, amd_pmc_suspend_handler, NULL);
+static const struct dev_pm_ops amd_pmc_pm = {
+ .suspend = amd_pmc_suspend_handler,
+};
static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
@@ -966,73 +727,12 @@ static const struct pci_device_id pmc_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PCO) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RV) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SP) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_SHP) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
{ }
};
-static int amd_pmc_s2d_init(struct amd_pmc_dev *dev)
-{
- u32 phys_addr_low, phys_addr_hi;
- u64 stb_phys_addr;
- u32 size = 0;
- int ret;
-
- /* Spill to DRAM feature uses separate SMU message port */
- dev->msg_port = 1;
-
- amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, dev->s2d_msg_id, true);
- if (size != S2D_TELEMETRY_BYTES_MAX)
- return -EIO;
-
- /* Get DRAM size */
- ret = amd_pmc_send_cmd(dev, S2D_DRAM_SIZE, &dev->dram_size, dev->s2d_msg_id, true);
- if (ret || !dev->dram_size)
- dev->dram_size = S2D_TELEMETRY_DRAMBYTES_MAX;
-
- /* Get STB DRAM address */
- amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, dev->s2d_msg_id, true);
- amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, dev->s2d_msg_id, true);
-
- stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low);
-
- /* Clear msg_port for other SMU operation */
- dev->msg_port = 0;
-
- dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, dev->dram_size);
- if (!dev->stb_virt_addr)
- return -ENOMEM;
-
- return 0;
-}
-
-static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data)
-{
- int err;
-
- err = amd_smn_write(0, AMD_PMC_STB_PMI_0, data);
- if (err) {
- dev_err(dev->dev, "failed to write data in stb: 0x%X\n", AMD_PMC_STB_PMI_0);
- return pcibios_err_to_errno(err);
- }
-
- return 0;
-}
-
-static int amd_pmc_read_stb(struct amd_pmc_dev *dev, u32 *buf)
-{
- int i, err;
-
- for (i = 0; i < FIFO_SIZE; i++) {
- err = amd_smn_read(0, AMD_PMC_STB_PMI_0, buf++);
- if (err) {
- dev_err(dev->dev, "error reading data from stb: 0x%X\n", AMD_PMC_STB_PMI_0);
- return pcibios_err_to_errno(err);
- }
- }
-
- return 0;
-}
-
static int amd_pmc_probe(struct platform_device *pdev)
{
struct amd_pmc_dev *dev = &pmc;
@@ -1043,7 +743,6 @@ static int amd_pmc_probe(struct platform_device *pdev)
u32 val;
dev->dev = &pdev->dev;
-
rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
if (!rdev || !pci_match_id(pmc_pci_ids, rdev)) {
err = -ENODEV;
@@ -1051,8 +750,7 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
dev->cpu_id = rdev->device;
-
- if (dev->cpu_id == AMD_CPU_ID_SP) {
+ if (dev->cpu_id == AMD_CPU_ID_SP || dev->cpu_id == AMD_CPU_ID_SHP) {
dev_warn_once(dev->dev, "S0i3 is not supported on this hardware\n");
err = -ENODEV;
goto err_pci_dev_put;
@@ -1067,7 +765,6 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
base_addr_lo = val & AMD_PMC_BASE_ADDR_HI_MASK;
-
err = amd_smn_read(0, AMD_PMC_BASE_ADDR_HI, &val);
if (err) {
dev_err(dev->dev, "error reading 0x%x\n", AMD_PMC_BASE_ADDR_HI);
@@ -1085,17 +782,13 @@ static int amd_pmc_probe(struct platform_device *pdev)
goto err_pci_dev_put;
}
- mutex_init(&dev->lock);
+ err = devm_mutex_init(dev->dev, &dev->lock);
+ if (err)
+ goto err_pci_dev_put;
/* Get num of IP blocks within the SoC */
amd_pmc_get_ip_info(dev);
- if (enable_stb && amd_pmc_is_stb_supported(dev)) {
- err = amd_pmc_s2d_init(dev);
- if (err)
- goto err_pci_dev_put;
- }
-
platform_set_drvdata(pdev, dev);
if (IS_ENABLED(CONFIG_SUSPEND)) {
err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops);
@@ -1106,6 +799,12 @@ static int amd_pmc_probe(struct platform_device *pdev)
}
amd_pmc_dbgfs_register(dev);
+ err = amd_stb_s2d_init(dev);
+ if (err)
+ goto err_pci_dev_put;
+
+ if (IS_ENABLED(CONFIG_AMD_MP2_STB))
+ amd_mp2_stb_init(dev);
pm_report_max_hw_sleep(U64_MAX);
return 0;
@@ -1122,7 +821,8 @@ static void amd_pmc_remove(struct platform_device *pdev)
acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops);
amd_pmc_dbgfs_unregister(dev);
pci_dev_put(dev->rdev);
- mutex_destroy(&dev->lock);
+ if (IS_ENABLED(CONFIG_AMD_MP2_STB))
+ amd_mp2_stb_deinit(dev);
}
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
@@ -1132,6 +832,7 @@ static const struct acpi_device_id amd_pmc_acpi_ids[] = {
{"AMDI0008", 0},
{"AMDI0009", 0},
{"AMDI000A", 0},
+ {"AMDI000B", 0},
{"AMD0004", 0},
{"AMD0005", 0},
{ }
@@ -1146,7 +847,7 @@ static struct platform_driver amd_pmc_driver = {
.pm = pm_sleep_ptr(&amd_pmc_pm),
},
.probe = amd_pmc_probe,
- .remove_new = amd_pmc_remove,
+ .remove = amd_pmc_remove,
};
module_platform_driver(amd_pmc_driver);
diff --git a/drivers/platform/x86/amd/pmc/pmc.h b/drivers/platform/x86/amd/pmc/pmc.h
index 827eef65e133..62f3e51020fd 100644
--- a/drivers/platform/x86/amd/pmc/pmc.h
+++ b/drivers/platform/x86/amd/pmc/pmc.h
@@ -14,34 +14,142 @@
#include <linux/types.h>
#include <linux/mutex.h>
+/* SMU communication registers */
+#define AMD_PMC_REGISTER_RESPONSE 0x980
+#define AMD_PMC_REGISTER_ARGUMENT 0x9BC
+
+/* PMC Scratch Registers */
+#define AMD_PMC_SCRATCH_REG_CZN 0x94
+#define AMD_PMC_SCRATCH_REG_YC 0xD14
+#define AMD_PMC_SCRATCH_REG_1AH 0xF14
+
+/* STB Registers */
+#define AMD_PMC_STB_S2IDLE_PREPARE 0xC6000001
+#define AMD_PMC_STB_S2IDLE_RESTORE 0xC6000002
+#define AMD_PMC_STB_S2IDLE_CHECK 0xC6000003
+
+/* Base address of SMU for mapping physical address to virtual address */
+#define AMD_PMC_MAPPING_SIZE 0x01000
+#define AMD_PMC_BASE_ADDR_OFFSET 0x10000
+#define AMD_PMC_BASE_ADDR_LO 0x13B102E8
+#define AMD_PMC_BASE_ADDR_HI 0x13B102EC
+#define AMD_PMC_BASE_ADDR_LO_MASK GENMASK(15, 0)
+#define AMD_PMC_BASE_ADDR_HI_MASK GENMASK(31, 20)
+
+/* SMU Response Codes */
+#define AMD_PMC_RESULT_OK 0x01
+#define AMD_PMC_RESULT_CMD_REJECT_BUSY 0xFC
+#define AMD_PMC_RESULT_CMD_REJECT_PREREQ 0xFD
+#define AMD_PMC_RESULT_CMD_UNKNOWN 0xFE
+#define AMD_PMC_RESULT_FAILED 0xFF
+
+/* FCH SSC Registers */
+#define FCH_S0I3_ENTRY_TIME_L_OFFSET 0x30
+#define FCH_S0I3_ENTRY_TIME_H_OFFSET 0x34
+#define FCH_S0I3_EXIT_TIME_L_OFFSET 0x38
+#define FCH_S0I3_EXIT_TIME_H_OFFSET 0x3C
+#define FCH_SSC_MAPPING_SIZE 0x800
+#define FCH_BASE_PHY_ADDR_LOW 0xFED81100
+#define FCH_BASE_PHY_ADDR_HIGH 0x00000000
+
+/* SMU Message Definations */
+#define SMU_MSG_GETSMUVERSION 0x02
+#define SMU_MSG_LOG_GETDRAM_ADDR_HI 0x04
+#define SMU_MSG_LOG_GETDRAM_ADDR_LO 0x05
+#define SMU_MSG_LOG_START 0x06
+#define SMU_MSG_LOG_RESET 0x07
+#define SMU_MSG_LOG_DUMP_DATA 0x08
+#define SMU_MSG_GET_SUP_CONSTRAINTS 0x09
+
+#define PMC_MSG_DELAY_MIN_US 50
+#define RESPONSE_REGISTER_LOOP_MAX 20000
+
+#define DELAY_MIN_US 2000
+#define DELAY_MAX_US 3000
+
+enum s2d_msg_port {
+ MSG_PORT_PMC,
+ MSG_PORT_S2D,
+};
+
+struct amd_mp2_dev {
+ void __iomem *mmio;
+ void __iomem *vslbase;
+ void *stbdata;
+ void *devres_gid;
+ struct pci_dev *pdev;
+ dma_addr_t dma_addr;
+ int stb_len;
+ bool is_stb_data;
+};
+
+struct stb_arg {
+ u32 s2d_msg_id;
+ u32 msg;
+ u32 arg;
+ u32 resp;
+};
+
struct amd_pmc_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
void __iomem *stb_virt_addr;
void __iomem *fch_virt_addr;
- bool msg_port;
u32 base_addr;
u32 cpu_id;
- u32 active_ips;
u32 dram_size;
+ u32 active_ips;
+ const struct amd_pmc_bit_map *ips_ptr;
u32 num_ips;
- u32 s2d_msg_id;
u32 smu_msg;
/* SMU version information */
u8 smu_program;
u8 major;
u8 minor;
u8 rev;
+ u8 msg_port;
struct device *dev;
struct pci_dev *rdev;
struct mutex lock; /* generic mutex lock */
struct dentry *dbgfs_dir;
struct quirk_entry *quirks;
bool disable_8042_wakeup;
+ struct amd_mp2_dev *mp2;
+ struct stb_arg stb_arg;
+};
+
+struct amd_pmc_bit_map {
+ const char *name;
+ u32 bit_mask;
+};
+
+struct smu_metrics {
+ u32 table_version;
+ u32 hint_count;
+ u32 s0i3_last_entry_status;
+ u32 timein_s0i2;
+ u64 timeentering_s0i3_lastcapture;
+ u64 timeentering_s0i3_totaltime;
+ u64 timeto_resume_to_os_lastcapture;
+ u64 timeto_resume_to_os_totaltime;
+ u64 timein_s0i3_lastcapture;
+ u64 timein_s0i3_totaltime;
+ u64 timein_swdrips_lastcapture;
+ u64 timein_swdrips_totaltime;
+ u64 timecondition_notmet_lastcapture[32];
+ u64 timecondition_notmet_totaltime[32];
+} __packed;
+
+enum amd_pmc_def {
+ MSG_TEST = 0x01,
+ MSG_OS_HINT_PCO,
+ MSG_OS_HINT_RN,
};
void amd_pmc_process_restore_quirks(struct amd_pmc_dev *dev);
void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
+void amd_mp2_stb_init(struct amd_pmc_dev *dev);
+void amd_mp2_stb_deinit(struct amd_pmc_dev *dev);
/* List of supported CPU ids */
#define AMD_CPU_ID_RV 0x15D0
@@ -52,6 +160,14 @@ void amd_pmc_quirks_init(struct amd_pmc_dev *dev);
#define AMD_CPU_ID_CB 0x14D8
#define AMD_CPU_ID_PS 0x14E8
#define AMD_CPU_ID_SP 0x14A4
+#define AMD_CPU_ID_SHP 0x153A
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
+#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
+#define PCI_DEVICE_ID_AMD_MP2_STB 0x172c
+
+int amd_stb_s2d_init(struct amd_pmc_dev *dev);
+int amd_stb_read(struct amd_pmc_dev *dev, u32 *buf);
+int amd_stb_write(struct amd_pmc_dev *dev, u32 data);
+int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret);
#endif /* PMC_H */
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
index f4fa8bd8bda8..25b8f7ae3abd 100644
--- a/drivers/platform/x86/amd/pmf/Kconfig
+++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -7,10 +7,11 @@ config AMD_PMF
tristate "AMD Platform Management Framework"
depends on ACPI && PCI
depends on POWER_SUPPLY
- depends on AMD_NB
+ depends on AMD_NODE
select ACPI_PLATFORM_PROFILE
depends on TEE && AMDTEE
depends on AMD_SFH_HID
+ depends on HAS_IOMEM
help
This driver provides support for the AMD Platform Management Framework.
The goal is to enhance end user experience by making AMD PCs smarter,
diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
index 7d6079b02589..5978464e0eb7 100644
--- a/drivers/platform/x86/amd/pmf/Makefile
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -4,7 +4,7 @@
# AMD Platform Management Framework
#
-obj-$(CONFIG_AMD_PMF) += amd-pmf.o
-amd-pmf-objs := core.o acpi.o sps.o \
- auto-mode.o cnqf.o \
- tee-if.o spc.o pmf-quirks.o
+obj-$(CONFIG_AMD_PMF) += amd-pmf.o
+amd-pmf-y := core.o acpi.o sps.o \
+ auto-mode.o cnqf.o \
+ tee-if.o spc.o
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index 1157ec148880..f75f7ecd8cd9 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -220,7 +220,7 @@ static void apmf_sbios_heartbeat_notify(struct work_struct *work)
if (!info)
return;
- schedule_delayed_work(&dev->heart_beat, msecs_to_jiffies(dev->hb_interval * 1000));
+ schedule_delayed_work(&dev->heart_beat, secs_to_jiffies(dev->hb_interval));
kfree(info);
}
@@ -282,6 +282,29 @@ int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx)
return 0;
}
+static int apmf_notify_smart_pc_update(struct amd_pmf_dev *pdev, u32 val, u32 preq, u32 index)
+{
+ struct amd_pmf_notify_smart_pc_update args;
+ struct acpi_buffer params;
+ union acpi_object *info;
+
+ args.size = sizeof(args);
+ args.pending_req = preq;
+ args.custom_bios[index] = val;
+
+ params.length = sizeof(args);
+ params.pointer = &args;
+
+ info = apmf_if_call(pdev, APMF_FUNC_NOTIFY_SMART_PC_UPDATES, &params);
+ if (!info)
+ return -EIO;
+
+ kfree(info);
+ dev_dbg(pdev->dev, "Notify smart pc update, val: %u\n", val);
+
+ return 0;
+}
+
int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
@@ -298,17 +321,29 @@ int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req
req, sizeof(*req));
}
+static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
+{
+ struct amd_pmf_dev *pmf_dev = data;
+ int ret;
+
+ guard(mutex)(&pmf_dev->cb_mutex);
+
+ ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req);
+ if (ret)
+ dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret);
+}
+
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
struct apmf_sbios_req req;
int ret;
- mutex_lock(&pmf_dev->update_mutex);
+ guard(mutex)(&pmf_dev->update_mutex);
ret = apmf_get_sbios_requests(pmf_dev, &req);
if (ret) {
dev_err(pmf_dev->dev, "Failed to get SBIOS requests:%d\n", ret);
- goto out;
+ return;
}
if (req.pending_req & BIT(APMF_AMT_NOTIFICATION)) {
@@ -330,8 +365,6 @@ static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
if (pmf_dev->amt_enabled)
amd_pmf_update_2_cql(pmf_dev, req.cql_event);
}
-out:
- mutex_unlock(&pmf_dev->update_mutex);
}
static int apmf_if_verify_interface(struct amd_pmf_dev *pdev)
@@ -407,44 +440,53 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
apmf_event_handler(ahandle, 0, pmf_dev);
}
+ if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2) {
+ status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
+ apmf_event_handler_v2, pmf_dev);
+ if (ACPI_FAILURE(status)) {
+ dev_err(pmf_dev->dev, "failed to install notify handler for custom BIOS inputs\n");
+ return -ENODEV;
+ }
+ }
+
return 0;
}
-static acpi_status apmf_walk_resources(struct acpi_resource *res, void *data)
+int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
{
- struct amd_pmf_dev *dev = data;
+ struct platform_device *pdev = to_platform_device(pmf_dev->dev);
- switch (res->type) {
- case ACPI_RESOURCE_TYPE_ADDRESS64:
- dev->policy_addr = res->data.address64.address.minimum;
- dev->policy_sz = res->data.address64.address.address_length;
- break;
- case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
- dev->policy_addr = res->data.fixed_memory32.address;
- dev->policy_sz = res->data.fixed_memory32.address_length;
- break;
+ pmf_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!pmf_dev->res) {
+ dev_dbg(pmf_dev->dev, "Failed to get I/O memory resource\n");
+ return -EINVAL;
}
- if (!dev->policy_addr || dev->policy_sz > POLICY_BUF_MAX_SZ || dev->policy_sz == 0) {
- pr_err("Incorrect Policy params, possibly a SBIOS bug\n");
- return AE_ERROR;
+ pmf_dev->policy_addr = pmf_dev->res->start;
+ /*
+ * We cannot use resource_size() here because it adds an extra byte to round off the size.
+ * In the case of PMF ResourceTemplate(), this rounding is already handled within the _CRS.
+ * Using resource_size() would increase the resource size by 1, causing a mismatch with the
+ * length field and leading to issues. Therefore, simply use end-start of the ACPI resource
+ * to obtain the actual length.
+ */
+ pmf_dev->policy_sz = pmf_dev->res->end - pmf_dev->res->start;
+
+ if (!pmf_dev->policy_addr || pmf_dev->policy_sz > POLICY_BUF_MAX_SZ ||
+ pmf_dev->policy_sz == 0) {
+ dev_err(pmf_dev->dev, "Incorrect policy params, possibly a SBIOS bug\n");
+ return -EINVAL;
}
- return AE_OK;
+ return 0;
}
-int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
+int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq, u32 idx)
{
- acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
- acpi_status status;
-
- status = acpi_walk_resources(ahandle, METHOD_NAME__CRS, apmf_walk_resources, pmf_dev);
- if (ACPI_FAILURE(status)) {
- dev_dbg(pmf_dev->dev, "acpi_walk_resources failed :%d\n", status);
+ if (!is_apmf_func_supported(dev, APMF_FUNC_NOTIFY_SMART_PC_UPDATES))
return -EINVAL;
- }
- return 0;
+ return apmf_notify_smart_pc_update(dev, val, preq, idx);
}
void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
@@ -457,6 +499,9 @@ void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
+
+ if (pmf_dev->smart_pc_enabled && pmf_dev->pmf_if_version == PMF_IF_V2)
+ acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler_v2);
}
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)
diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
index 02ff68be10d0..a184922bba8d 100644
--- a/drivers/platform/x86/amd/pmf/auto-mode.c
+++ b/drivers/platform/x86/amd/pmf/auto-mode.c
@@ -120,9 +120,9 @@ static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
+ fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_APU]), NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
+ fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev, config_store.mode_set[idx].fan_control.manual,
diff --git a/drivers/platform/x86/amd/pmf/cnqf.c b/drivers/platform/x86/amd/pmf/cnqf.c
index bc8899e15c91..207a0b33d8d3 100644
--- a/drivers/platform/x86/amd/pmf/cnqf.c
+++ b/drivers/platform/x86/amd/pmf/cnqf.c
@@ -81,10 +81,10 @@ static int amd_pmf_set_cnqf(struct amd_pmf_dev *dev, int src, int idx,
amd_pmf_send_cmd(dev, SET_SPPT, false, pc->sppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pc->sppt_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pc->stt_min, NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false, pc->stt_skin_temp[STT_TEMP_APU],
- NULL);
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false, pc->stt_skin_temp[STT_TEMP_HS2],
- NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
+ fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_APU]), NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
+ fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev,
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 64e6e34a2a9a..76910601cac8 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -8,13 +8,13 @@
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
-#include <asm/amd_nb.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
+#include <asm/amd/node.h>
#include "pmf.h"
/* PMF-SMU communication registers */
@@ -37,11 +37,6 @@
#define AMD_PMF_RESULT_CMD_UNKNOWN 0xFE
#define AMD_PMF_RESULT_FAILED 0xFF
-/* List of supported CPU ids */
-#define AMD_CPU_ID_RMB 0x14b5
-#define AMD_CPU_ID_PS 0x14e8
-#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
-
#define PMF_MSG_DELAY_MIN_US 50
#define RESPONSE_REGISTER_LOOP_MAX 20000
@@ -132,7 +127,8 @@ static void amd_pmf_get_metrics(struct work_struct *work)
ktime_t time_elapsed_ms;
int socket_power;
- mutex_lock(&dev->update_mutex);
+ guard(mutex)(&dev->update_mutex);
+
/* Transfer table contents */
memset(dev->buf, 0, sizeof(dev->m_table));
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
@@ -154,7 +150,6 @@ static void amd_pmf_get_metrics(struct work_struct *work)
dev->start_time = ktime_to_ms(ktime_get());
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
- mutex_unlock(&dev->update_mutex);
}
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
@@ -181,12 +176,26 @@ static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
+/**
+ * fixp_q88_fromint: Convert integer to Q8.8
+ * @val: input value
+ *
+ * Converts an integer into binary fixed point format where 8 bits
+ * are used for integer and 8 bits are used for the decimal.
+ *
+ * Return: unsigned integer converted to Q8.8 format
+ */
+u32 fixp_q88_fromint(u32 val)
+{
+ return val << 8;
+}
+
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
{
int rc;
u32 val;
- mutex_lock(&dev->lock);
+ guard(mutex)(&dev->lock);
/* Wait until we get a valid response */
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
@@ -194,7 +203,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "failed to talk to SMU\n");
- goto out_unlock;
+ return rc;
}
/* Write zero to response register */
@@ -212,7 +221,7 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "SMU response timed out\n");
- goto out_unlock;
+ return rc;
}
switch (val) {
@@ -226,21 +235,19 @@ int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32
case AMD_PMF_RESULT_CMD_REJECT_BUSY:
dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val);
rc = -EBUSY;
- goto out_unlock;
+ break;
case AMD_PMF_RESULT_CMD_UNKNOWN:
dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val);
rc = -EINVAL;
- goto out_unlock;
+ break;
case AMD_PMF_RESULT_CMD_REJECT_PREREQ:
case AMD_PMF_RESULT_FAILED:
default:
dev_err(dev->dev, "SMU cmd failed. err: 0x%x\n", val);
rc = -EIO;
- goto out_unlock;
+ break;
}
-out_unlock:
- mutex_unlock(&dev->lock);
amd_pmf_dump_registers(dev);
return rc;
}
@@ -249,6 +256,7 @@ static const struct pci_device_id pmf_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
+ { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
{ }
};
@@ -259,7 +267,20 @@ int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
/* Get Metrics Table Address */
if (alloc_buffer) {
- dev->buf = kzalloc(sizeof(dev->m_table), GFP_KERNEL);
+ switch (dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ case AMD_CPU_ID_RMB:
+ dev->mtable_size = sizeof(dev->m_table);
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ dev->mtable_size = sizeof(dev->m_table_v2);
+ break;
+ default:
+ dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
+ }
+
+ dev->buf = kzalloc(dev->mtable_size, GFP_KERNEL);
if (!dev->buf)
return -ENOMEM;
}
@@ -364,7 +385,6 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR) ||
is_apmf_func_supported(dev, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
power_supply_unreg_notifier(&dev->pwr_src_notifier);
- amd_pmf_deinit_sps(dev);
}
if (dev->smart_pc_enabled) {
@@ -381,6 +401,8 @@ static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0100", 0x100},
{"AMDI0102", 0},
{"AMDI0103", 0},
+ {"AMDI0105", 0},
+ {"AMDI0107", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
@@ -419,18 +441,18 @@ static int amd_pmf_probe(struct platform_device *pdev)
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_LO, &val);
if (err) {
- dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
pci_dev_put(rdev);
- return pcibios_err_to_errno(err);
+ return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
+ "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
}
base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_HI, &val);
if (err) {
- dev_err(dev->dev, "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
pci_dev_put(rdev);
- return pcibios_err_to_errno(err);
+ return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
+ "error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
}
base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
@@ -444,8 +466,8 @@ static int amd_pmf_probe(struct platform_device *pdev)
mutex_init(&dev->lock);
mutex_init(&dev->update_mutex);
+ mutex_init(&dev->cb_mutex);
- amd_pmf_quirks_init(dev);
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
@@ -470,6 +492,7 @@ static void amd_pmf_remove(struct platform_device *pdev)
amd_pmf_dbgfs_unregister(dev);
mutex_destroy(&dev->lock);
mutex_destroy(&dev->update_mutex);
+ mutex_destroy(&dev->cb_mutex);
kfree(dev->buf);
}
@@ -486,7 +509,7 @@ static struct platform_driver amd_pmf_driver = {
.pm = pm_sleep_ptr(&amd_pmf_pm),
},
.probe = amd_pmf_probe,
- .remove_new = amd_pmf_remove,
+ .remove = amd_pmf_remove,
};
module_platform_driver(amd_pmf_driver);
diff --git a/drivers/platform/x86/amd/pmf/pmf-quirks.c b/drivers/platform/x86/amd/pmf/pmf-quirks.c
deleted file mode 100644
index 0b2eb0ae85fe..000000000000
--- a/drivers/platform/x86/amd/pmf/pmf-quirks.c
+++ /dev/null
@@ -1,51 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * AMD Platform Management Framework Driver Quirks
- *
- * Copyright (c) 2024, Advanced Micro Devices, Inc.
- * All Rights Reserved.
- *
- * Author: Mario Limonciello <mario.limonciello@amd.com>
- */
-
-#include <linux/dmi.h>
-
-#include "pmf.h"
-
-struct quirk_entry {
- u32 supported_func;
-};
-
-static struct quirk_entry quirk_no_sps_bug = {
- .supported_func = 0x4003,
-};
-
-static const struct dmi_system_id fwbug_list[] = {
- {
- .ident = "ROG Zephyrus G14",
- .matches = {
- DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
- DMI_MATCH(DMI_PRODUCT_NAME, "GA403UV"),
- },
- .driver_data = &quirk_no_sps_bug,
- },
- {}
-};
-
-void amd_pmf_quirks_init(struct amd_pmf_dev *dev)
-{
- const struct dmi_system_id *dmi_id;
- struct quirk_entry *quirks;
-
- dmi_id = dmi_first_match(fwbug_list);
- if (!dmi_id)
- return;
-
- quirks = dmi_id->driver_data;
- if (quirks->supported_func) {
- dev->supported_func = quirks->supported_func;
- pr_info("Using supported funcs quirk to avoid %s platform firmware bug\n",
- dmi_id->ident);
- }
-}
-
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index eeedd0c0395a..45b60238d527 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -12,12 +12,20 @@
#define PMF_H
#include <linux/acpi.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#define POLICY_BUF_MAX_SZ 0x4b000
#define POLICY_SIGN_COOKIE 0x31535024
#define POLICY_COOKIE_OFFSET 0x10
+/* List of supported CPU ids */
+#define AMD_CPU_ID_RMB 0x14b5
+#define AMD_CPU_ID_PS 0x14e8
+#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
+#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
+
struct cookie_header {
u32 sign;
u32 length;
@@ -34,6 +42,7 @@ struct cookie_header {
#define APMF_FUNC_STATIC_SLIDER_GRANULAR 9
#define APMF_FUNC_DYN_SLIDER_AC 11
#define APMF_FUNC_DYN_SLIDER_DC 12
+#define APMF_FUNC_NOTIFY_SMART_PC_UPDATES 14
#define APMF_FUNC_SBIOS_HEARTBEAT_V2 16
/* Message Definitions */
@@ -81,15 +90,28 @@ struct cookie_header {
#define PMF_POLICY_STT_SKINTEMP_APU 7
#define PMF_POLICY_STT_SKINTEMP_HS2 8
#define PMF_POLICY_SYSTEM_STATE 9
+#define PMF_POLICY_BIOS_OUTPUT_1 10
+#define PMF_POLICY_BIOS_OUTPUT_2 11
#define PMF_POLICY_P3T 38
+#define PMF_POLICY_BIOS_OUTPUT_3 57
+#define PMF_POLICY_BIOS_OUTPUT_4 58
+#define PMF_POLICY_BIOS_OUTPUT_5 59
+#define PMF_POLICY_BIOS_OUTPUT_6 60
+#define PMF_POLICY_BIOS_OUTPUT_7 61
+#define PMF_POLICY_BIOS_OUTPUT_8 62
+#define PMF_POLICY_BIOS_OUTPUT_9 63
+#define PMF_POLICY_BIOS_OUTPUT_10 64
/* TA macros */
#define PMF_TA_IF_VERSION_MAJOR 1
#define TA_PMF_ACTION_MAX 32
#define TA_PMF_UNDO_MAX 8
-#define TA_OUTPUT_RESERVED_MEM 906
+#define TA_OUTPUT_RESERVED_MEM 922
#define MAX_OPERATION_PARAMS 4
+#define TA_ERROR_CRYPTO_INVALID_PARAM 0x20002
+#define TA_ERROR_CRYPTO_BIN_TOO_LARGE 0x2000d
+
#define PMF_IF_V1 1
#define PMF_IF_V2 2
@@ -180,6 +202,53 @@ struct apmf_fan_idx {
u32 fan_ctl_idx;
} __packed;
+struct smu_pmf_metrics_v2 {
+ u16 core_frequency[16]; /* MHz */
+ u16 core_power[16]; /* mW */
+ u16 core_temp[16]; /* centi-C */
+ u16 gfx_temp; /* centi-C */
+ u16 soc_temp; /* centi-C */
+ u16 stapm_opn_limit; /* mW */
+ u16 stapm_cur_limit; /* mW */
+ u16 infra_cpu_maxfreq; /* MHz */
+ u16 infra_gfx_maxfreq; /* MHz */
+ u16 skin_temp; /* centi-C */
+ u16 gfxclk_freq; /* MHz */
+ u16 fclk_freq; /* MHz */
+ u16 gfx_activity; /* GFX busy % [0-100] */
+ u16 socclk_freq; /* MHz */
+ u16 vclk_freq; /* MHz */
+ u16 vcn_activity; /* VCN busy % [0-100] */
+ u16 vpeclk_freq; /* MHz */
+ u16 ipuclk_freq; /* MHz */
+ u16 ipu_busy[8]; /* NPU busy % [0-100] */
+ u16 dram_reads; /* MB/sec */
+ u16 dram_writes; /* MB/sec */
+ u16 core_c0residency[16]; /* C0 residency % [0-100] */
+ u16 ipu_power; /* mW */
+ u32 apu_power; /* mW */
+ u32 gfx_power; /* mW */
+ u32 dgpu_power; /* mW */
+ u32 socket_power; /* mW */
+ u32 all_core_power; /* mW */
+ u32 filter_alpha_value; /* time constant [us] */
+ u32 metrics_counter;
+ u16 memclk_freq; /* MHz */
+ u16 mpipuclk_freq; /* MHz */
+ u16 ipu_reads; /* MB/sec */
+ u16 ipu_writes; /* MB/sec */
+ u32 throttle_residency_prochot;
+ u32 throttle_residency_spl;
+ u32 throttle_residency_fppt;
+ u32 throttle_residency_sppt;
+ u32 throttle_residency_thm_core;
+ u32 throttle_residency_thm_gfx;
+ u32 throttle_residency_thm_soc;
+ u16 psys;
+ u16 spare1;
+ u32 spare[6];
+} __packed;
+
struct smu_pmf_metrics {
u16 gfxclk_freq; /* in MHz */
u16 socclk_freq; /* in MHz */
@@ -272,11 +341,12 @@ struct amd_pmf_dev {
struct mutex lock; /* protects the PMF interface */
u32 supported_func;
enum platform_profile_option current_profile;
- struct platform_profile_handler pprof;
+ struct device *ppdev; /* platform profile class device */
struct dentry *dbgfs_dir;
int hb_interval; /* SBIOS heartbeat interval */
struct delayed_work heart_beat;
struct smu_pmf_metrics m_table;
+ struct smu_pmf_metrics_v2 m_table_v2;
struct delayed_work work_buffer;
ktime_t start_time;
int socket_power_history[AVG_SAMPLE_SIZE];
@@ -289,17 +359,22 @@ struct amd_pmf_dev {
/* Smart PC solution builder */
struct dentry *esbin;
unsigned char *policy_buf;
- u32 policy_sz;
+ resource_size_t policy_sz;
struct tee_context *tee_ctx;
struct tee_shm *fw_shm_pool;
u32 session_id;
void *shbuf;
struct delayed_work pb_work;
struct pmf_action_table *prev_data;
- u64 policy_addr;
+ resource_size_t policy_addr;
void __iomem *policy_base;
bool smart_pc_enabled;
u16 pmf_if_version;
+ struct input_dev *pmf_idev;
+ size_t mtable_size;
+ struct resource *res;
+ struct apmf_sbios_req_v2 req; /* To get custom bios pending request */
+ struct mutex cb_mutex;
};
struct apmf_sps_prop_granular_v2 {
@@ -342,6 +417,12 @@ struct os_power_slider {
u8 slider_event;
} __packed;
+struct amd_pmf_notify_smart_pc_update {
+ u16 size;
+ u32 pending_req;
+ u32 custom_bios[10];
+} __packed;
+
struct fan_table_control {
bool manual;
unsigned long fan_id;
@@ -540,6 +621,30 @@ enum ta_slider {
TA_MAX,
};
+enum apmf_smartpc_custom_bios_inputs {
+ APMF_SMARTPC_CUSTOM_BIOS_INPUT1,
+ APMF_SMARTPC_CUSTOM_BIOS_INPUT2,
+};
+
+enum apmf_preq_smartpc {
+ NOTIFY_CUSTOM_BIOS_INPUT1 = 5,
+ NOTIFY_CUSTOM_BIOS_INPUT2,
+};
+
+enum platform_type {
+ PTYPE_UNKNOWN = 0,
+ LID_CLOSE,
+ CLAMSHELL,
+ FLAT,
+ TENT,
+ STAND,
+ TABLET,
+ BOOK,
+ PRESENTATION,
+ PULL_FWD,
+ PTYPE_INVALID = 0xf,
+};
+
/* Command ids for TA communication */
enum ta_pmf_command {
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
@@ -581,7 +686,8 @@ struct ta_pmf_condition_info {
u32 power_slider;
u32 lid_state;
bool user_present;
- u32 rsvd1[2];
+ u32 bios_input1;
+ u32 bios_input2;
u32 monitor_count;
u32 rsvd2[2];
u32 bat_design;
@@ -591,7 +697,9 @@ struct ta_pmf_condition_info {
u32 device_state;
u32 socket_power;
u32 skin_temperature;
- u32 rsvd3[5];
+ u32 rsvd3[2];
+ u32 platform_type;
+ u32 rsvd3_1[2];
u32 ambient_light;
u32 length;
u32 avg_c0residency;
@@ -669,13 +777,13 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev);
int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
+u32 fixp_q88_fromint(u32 val);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
struct amd_pmf_static_slider_granular *table);
int amd_pmf_init_sps(struct amd_pmf_dev *dev);
-void amd_pmf_deinit_sps(struct amd_pmf_dev *dev);
int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
struct apmf_static_slider_granular_output *output);
bool is_pprof_balanced(struct amd_pmf_dev *pmf);
@@ -715,12 +823,10 @@ extern const struct attribute_group cnqf_feature_attribute_group;
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev);
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev);
int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev);
+int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq, u32 idx);
/* Smart PC - TA interfaces */
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
-/* Quirk infrastructure */
-void amd_pmf_quirks_init(struct amd_pmf_dev *dev);
-
#endif /* PMF_H */
diff --git a/drivers/platform/x86/amd/pmf/spc.c b/drivers/platform/x86/amd/pmf/spc.c
index a3dec14c3004..1d90f9382024 100644
--- a/drivers/platform/x86/amd/pmf/spc.c
+++ b/drivers/platform/x86/amd/pmf/spc.c
@@ -16,6 +16,46 @@
#include "pmf.h"
#ifdef CONFIG_AMD_PMF_DEBUG
+static const char *platform_type_as_str(u16 platform_type)
+{
+ switch (platform_type) {
+ case CLAMSHELL:
+ return "CLAMSHELL";
+ case FLAT:
+ return "FLAT";
+ case TENT:
+ return "TENT";
+ case STAND:
+ return "STAND";
+ case TABLET:
+ return "TABLET";
+ case BOOK:
+ return "BOOK";
+ case PRESENTATION:
+ return "PRESENTATION";
+ case PULL_FWD:
+ return "PULL_FWD";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static const char *laptop_placement_as_str(u16 device_state)
+{
+ switch (device_state) {
+ case ON_TABLE:
+ return "ON_TABLE";
+ case ON_LAP_MOTION:
+ return "ON_LAP_MOTION";
+ case IN_BAG:
+ return "IN_BAG";
+ case OUT_OF_BAG:
+ return "OUT_OF_BAG";
+ default:
+ return "UNKNOWN";
+ }
+}
+
static const char *ta_slider_as_str(unsigned int state)
{
switch (state) {
@@ -47,36 +87,82 @@ void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *
dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open");
dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away");
dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light);
+ dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
+ dev_dbg(dev->dev, "Laptop placement: %s\n",
+ laptop_placement_as_str(in->ev_info.device_state));
+ dev_dbg(dev->dev, "Custom BIOS input1: %u\n", in->ev_info.bios_input1);
+ dev_dbg(dev->dev, "Custom BIOS input2: %u\n", in->ev_info.bios_input2);
dev_dbg(dev->dev, "==== TA inputs END ====\n");
}
#else
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
#endif
-static void amd_pmf_get_smu_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
+static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
+ struct ta_pmf_enact_table *in)
{
- u16 max, avg = 0;
- int i;
+ if (!pdev->req.pending_req)
+ return;
- memset(dev->buf, 0, sizeof(dev->m_table));
- amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
- memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
+ switch (pdev->req.pending_req) {
+ case BIT(NOTIFY_CUSTOM_BIOS_INPUT1):
+ in->ev_info.bios_input1 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT1];
+ break;
+ case BIT(NOTIFY_CUSTOM_BIOS_INPUT2):
+ in->ev_info.bios_input2 = pdev->req.custom_policy[APMF_SMARTPC_CUSTOM_BIOS_INPUT2];
+ break;
+ default:
+ dev_dbg(pdev->dev, "Invalid preq for BIOS input: 0x%x\n", pdev->req.pending_req);
+ }
- in->ev_info.socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
- in->ev_info.skin_temperature = dev->m_table.skin_temp;
+ /* Clear pending requests after handling */
+ memset(&pdev->req, 0, sizeof(pdev->req));
+}
+
+static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
+{
+ u16 max, avg = 0;
+ int i;
/* Get the avg and max C0 residency of all the cores */
- max = dev->m_table.avg_core_c0residency[0];
- for (i = 0; i < ARRAY_SIZE(dev->m_table.avg_core_c0residency); i++) {
- avg += dev->m_table.avg_core_c0residency[i];
- if (dev->m_table.avg_core_c0residency[i] > max)
- max = dev->m_table.avg_core_c0residency[i];
+ max = *core_res;
+ for (i = 0; i < size; i++) {
+ avg += core_res[i];
+ if (core_res[i] > max)
+ max = core_res[i];
}
-
- avg = DIV_ROUND_CLOSEST(avg, ARRAY_SIZE(dev->m_table.avg_core_c0residency));
+ avg = DIV_ROUND_CLOSEST(avg, size);
in->ev_info.avg_c0residency = avg;
in->ev_info.max_c0residency = max;
- in->ev_info.gfx_busy = dev->m_table.avg_gfx_activity;
+}
+
+static void amd_pmf_get_smu_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
+{
+ /* Get the updated metrics table data */
+ memset(dev->buf, 0, dev->mtable_size);
+ amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, 0, 7, NULL);
+
+ switch (dev->cpu_id) {
+ case AMD_CPU_ID_PS:
+ memcpy(&dev->m_table, dev->buf, dev->mtable_size);
+ in->ev_info.socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
+ in->ev_info.skin_temperature = dev->m_table.skin_temp;
+ in->ev_info.gfx_busy = dev->m_table.avg_gfx_activity;
+ amd_pmf_get_c0_residency(dev->m_table.avg_core_c0residency,
+ ARRAY_SIZE(dev->m_table.avg_core_c0residency), in);
+ break;
+ case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
+ case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
+ memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
+ in->ev_info.socket_power = dev->m_table_v2.apu_power + dev->m_table_v2.dgpu_power;
+ in->ev_info.skin_temperature = dev->m_table_v2.skin_temp;
+ in->ev_info.gfx_busy = dev->m_table_v2.gfx_activity;
+ amd_pmf_get_c0_residency(dev->m_table_v2.core_c0residency,
+ ARRAY_SIZE(dev->m_table_v2.core_c0residency), in);
+ break;
+ default:
+ dev_err(dev->dev, "Unsupported CPU id: 0x%x", dev->cpu_id);
+ }
}
static const char * const pmf_battery_supply_name[] = {
@@ -133,12 +219,14 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_
switch (dev->current_profile) {
case PLATFORM_PROFILE_PERFORMANCE:
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
val = TA_BEST_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
val = TA_BETTER_PERFORMANCE;
break;
case PLATFORM_PROFILE_LOW_POWER:
+ case PLATFORM_PROFILE_QUIET:
val = TA_BEST_BATTERY;
break;
default:
@@ -150,36 +238,34 @@ static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_
return 0;
}
-static int amd_pmf_get_sensor_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
+static void amd_pmf_get_sensor_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
struct amd_sfh_info sfh_info;
- int ret;
+
+ /* Get the latest information from SFH */
+ in->ev_info.user_present = false;
/* Get ALS data */
- ret = amd_get_sfh_info(&sfh_info, MT_ALS);
- if (!ret)
+ if (!amd_get_sfh_info(&sfh_info, MT_ALS))
in->ev_info.ambient_light = sfh_info.ambient_light;
else
- return ret;
+ dev_dbg(dev->dev, "ALS is not enabled/detected\n");
/* get HPD data */
- ret = amd_get_sfh_info(&sfh_info, MT_HPD);
- if (ret)
- return ret;
-
- switch (sfh_info.user_present) {
- case SFH_NOT_DETECTED:
- in->ev_info.user_present = 0xff; /* assume no sensors connected */
- break;
- case SFH_USER_PRESENT:
- in->ev_info.user_present = 1;
- break;
- case SFH_USER_AWAY:
- in->ev_info.user_present = 0;
- break;
+ if (!amd_get_sfh_info(&sfh_info, MT_HPD)) {
+ if (sfh_info.user_present == SFH_USER_PRESENT)
+ in->ev_info.user_present = true;
+ } else {
+ dev_dbg(dev->dev, "HPD is not enabled/detected\n");
}
- return 0;
+ /* Get SRA (Secondary Accelerometer) data */
+ if (!amd_get_sfh_info(&sfh_info, MT_SRA)) {
+ in->ev_info.platform_type = sfh_info.platform_type;
+ in->ev_info.device_state = sfh_info.laptop_placement;
+ } else {
+ dev_dbg(dev->dev, "SRA is not enabled/detected\n");
+ }
}
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
@@ -191,4 +277,5 @@ void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_tab
amd_pmf_get_battery_info(dev, in);
amd_pmf_get_slider_info(dev, in);
amd_pmf_get_sensor_info(dev, in);
+ amd_pmf_get_custom_bios_inputs(dev, in);
}
diff --git a/drivers/platform/x86/amd/pmf/sps.c b/drivers/platform/x86/amd/pmf/sps.c
index 92f7fb22277d..49e14ca94a9e 100644
--- a/drivers/platform/x86/amd/pmf/sps.c
+++ b/drivers/platform/x86/amd/pmf/sps.c
@@ -198,9 +198,11 @@ static void amd_pmf_update_slider_v2(struct amd_pmf_dev *dev, int idx)
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false,
apts_config_store.val[idx].stt_min_limit, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- apts_config_store.val[idx].stt_skin_temp_limit_apu, NULL);
+ fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_apu),
+ NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- apts_config_store.val[idx].stt_skin_temp_limit_hs2, NULL);
+ fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_hs2),
+ NULL);
}
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
@@ -217,9 +219,11 @@ void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false,
config_store.prop[src][idx].stt_min, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
- config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU], NULL);
+ fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU]),
+ NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
- config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2], NULL);
+ fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2]),
+ NULL);
} else if (op == SLIDER_OP_GET) {
amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE, &table->prop[src][idx].spl);
amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE, &table->prop[src][idx].fppt);
@@ -282,10 +286,10 @@ bool is_pprof_balanced(struct amd_pmf_dev *pmf)
return (pmf->current_profile == PLATFORM_PROFILE_BALANCED) ? true : false;
}
-static int amd_pmf_profile_get(struct platform_profile_handler *pprof,
+static int amd_pmf_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
- struct amd_pmf_dev *pmf = container_of(pprof, struct amd_pmf_dev, pprof);
+ struct amd_pmf_dev *pmf = dev_get_drvdata(dev);
*profile = pmf->current_profile;
return 0;
@@ -297,12 +301,14 @@ int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf)
switch (pmf->current_profile) {
case PLATFORM_PROFILE_PERFORMANCE:
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
mode = POWER_MODE_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
mode = POWER_MODE_BALANCED_POWER;
break;
case PLATFORM_PROFILE_LOW_POWER:
+ case PLATFORM_PROFILE_QUIET:
mode = POWER_MODE_POWER_SAVER;
break;
default:
@@ -363,10 +369,10 @@ int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev)
return 0;
}
-static int amd_pmf_profile_set(struct platform_profile_handler *pprof,
+static int amd_pmf_profile_set(struct device *dev,
enum platform_profile_option profile)
{
- struct amd_pmf_dev *pmf = container_of(pprof, struct amd_pmf_dev, pprof);
+ struct amd_pmf_dev *pmf = dev_get_drvdata(dev);
int ret = 0;
pmf->current_profile = profile;
@@ -387,10 +393,32 @@ static int amd_pmf_profile_set(struct platform_profile_handler *pprof,
return 0;
}
-int amd_pmf_init_sps(struct amd_pmf_dev *dev)
+static int amd_pmf_hidden_choices(void *drvdata, unsigned long *choices)
{
- int err;
+ set_bit(PLATFORM_PROFILE_QUIET, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
+
+ return 0;
+}
+
+static int amd_pmf_profile_probe(void *drvdata, unsigned long *choices)
+{
+ set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
+ set_bit(PLATFORM_PROFILE_BALANCED, choices);
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
+
+ return 0;
+}
+static const struct platform_profile_ops amd_pmf_profile_ops = {
+ .probe = amd_pmf_profile_probe,
+ .hidden_choices = amd_pmf_hidden_choices,
+ .profile_get = amd_pmf_profile_get,
+ .profile_set = amd_pmf_profile_set,
+};
+
+int amd_pmf_init_sps(struct amd_pmf_dev *dev)
+{
dev->current_profile = PLATFORM_PROFILE_BALANCED;
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
@@ -405,24 +433,12 @@ int amd_pmf_init_sps(struct amd_pmf_dev *dev)
amd_pmf_set_sps_power_limits(dev);
}
- dev->pprof.profile_get = amd_pmf_profile_get;
- dev->pprof.profile_set = amd_pmf_profile_set;
-
- /* Setup supported modes */
- set_bit(PLATFORM_PROFILE_LOW_POWER, dev->pprof.choices);
- set_bit(PLATFORM_PROFILE_BALANCED, dev->pprof.choices);
- set_bit(PLATFORM_PROFILE_PERFORMANCE, dev->pprof.choices);
-
/* Create platform_profile structure and register */
- err = platform_profile_register(&dev->pprof);
- if (err)
- dev_err(dev->dev, "Failed to register SPS support, this is most likely an SBIOS bug: %d\n",
- err);
-
- return err;
-}
+ dev->ppdev = devm_platform_profile_register(dev->dev, "amd-pmf", dev,
+ &amd_pmf_profile_ops);
+ if (IS_ERR(dev->ppdev))
+ dev_err(dev->dev, "Failed to register SPS support, this is most likely an SBIOS bug: %ld\n",
+ PTR_ERR(dev->ppdev));
-void amd_pmf_deinit_sps(struct amd_pmf_dev *dev)
-{
- platform_profile_remove();
+ return PTR_ERR_OR_ZERO(dev->ppdev);
}
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index b438de4d6bfc..d3bd12ad036a 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -27,8 +27,11 @@ module_param(pb_side_load, bool, 0444);
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
#endif
-static const uuid_t amd_pmf_ta_uuid = UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d,
- 0xb1, 0x2d, 0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43);
+static const uuid_t amd_pmf_ta_uuid[] = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8, 0x8a,
+ 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
+ UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d, 0xc5,
+ 0x29, 0xb1, 0x3d, 0x85, 0x43),
+ };
static const char *amd_pmf_uevent_as_str(unsigned int state)
{
@@ -62,18 +65,12 @@ static void amd_pmf_prepare_args(struct amd_pmf_dev *dev, int cmd,
param[0].u.memref.shm_offs = 0;
}
-static int amd_pmf_update_uevents(struct amd_pmf_dev *dev, u16 event)
+static void amd_pmf_update_uevents(struct amd_pmf_dev *dev, u16 event)
{
- char *envp[2] = {};
-
- envp[0] = kasprintf(GFP_KERNEL, "EVENT_ID=%d", event);
- if (!envp[0])
- return -EINVAL;
-
- kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, envp);
-
- kfree(envp[0]);
- return 0;
+ input_report_key(dev->pmf_idev, event, 1); /* key press */
+ input_sync(dev->pmf_idev);
+ input_report_key(dev->pmf_idev, event, 0); /* key release */
+ input_sync(dev->pmf_idev);
}
static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out)
@@ -126,7 +123,8 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_STT_SKINTEMP_APU:
if (dev->prev_data->stt_skintemp_apu != val) {
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
+ fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_APU: %u\n", val);
dev->prev_data->stt_skintemp_apu = val;
}
@@ -134,7 +132,8 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
case PMF_POLICY_STT_SKINTEMP_HS2:
if (dev->prev_data->stt_skintemp_hs2 != val) {
- amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false, val, NULL);
+ amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
+ fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_HS2: %u\n", val);
dev->prev_data->stt_skintemp_hs2 = val;
}
@@ -149,10 +148,63 @@ static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_
break;
case PMF_POLICY_SYSTEM_STATE:
- amd_pmf_update_uevents(dev, val);
+ switch (val) {
+ case 0:
+ amd_pmf_update_uevents(dev, KEY_SLEEP);
+ break;
+ case 1:
+ amd_pmf_update_uevents(dev, KEY_SUSPEND);
+ break;
+ case 2:
+ amd_pmf_update_uevents(dev, KEY_SCREENLOCK);
+ break;
+ default:
+ dev_err(dev->dev, "Invalid PMF policy system state: %d\n", val);
+ }
+
dev_dbg(dev->dev, "update SYSTEM_STATE: %s\n",
amd_pmf_uevent_as_str(val));
break;
+
+ case PMF_POLICY_BIOS_OUTPUT_1:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(0), 0);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_2:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(1), 1);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_3:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(2), 2);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_4:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(3), 3);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_5:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(4), 4);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_6:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(5), 5);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_7:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(6), 6);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_8:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(7), 7);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_9:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(8), 8);
+ break;
+
+ case PMF_POLICY_BIOS_OUTPUT_10:
+ amd_pmf_smartpc_apply_bios_output(dev, val, BIT(9), 9);
+ break;
}
}
}
@@ -210,7 +262,7 @@ static int amd_pmf_invoke_cmd_init(struct amd_pmf_dev *dev)
return -ENODEV;
}
- dev_dbg(dev->dev, "Policy Binary size: %u bytes\n", dev->policy_sz);
+ dev_dbg(dev->dev, "Policy Binary size: %llu bytes\n", (unsigned long long)dev->policy_sz);
memset(dev->shbuf, 0, dev->policy_sz);
ta_sm = dev->shbuf;
in = &ta_sm->pmf_input.init_table;
@@ -274,14 +326,19 @@ static int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
*/
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms * 3));
} else {
- dev_err(dev->dev, "ta invoke cmd init failed err: %x\n", res);
+ dev_dbg(dev->dev, "ta invoke cmd init failed err: %x\n", res);
dev->smart_pc_enabled = false;
- return -EIO;
+ return res;
}
return 0;
}
+static inline bool amd_pmf_pb_valid(struct amd_pmf_dev *dev)
+{
+ return memchr_inv(dev->policy_buf, 0xff, dev->policy_sz);
+}
+
#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_hex_dump_pb(struct amd_pmf_dev *dev)
{
@@ -301,25 +358,30 @@ static ssize_t amd_pmf_get_pb_data(struct file *filp, const char __user *buf,
return -EINVAL;
/* re-alloc to the new buffer length of the policy binary */
- new_policy_buf = kzalloc(length, GFP_KERNEL);
- if (!new_policy_buf)
- return -ENOMEM;
-
- if (copy_from_user(new_policy_buf, buf, length)) {
- kfree(new_policy_buf);
- return -EFAULT;
- }
+ new_policy_buf = memdup_user(buf, length);
+ if (IS_ERR(new_policy_buf))
+ return PTR_ERR(new_policy_buf);
kfree(dev->policy_buf);
dev->policy_buf = new_policy_buf;
dev->policy_sz = length;
+ if (!amd_pmf_pb_valid(dev)) {
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
amd_pmf_hex_dump_pb(dev);
ret = amd_pmf_start_policy_engine(dev);
if (ret < 0)
- return ret;
+ goto cleanup;
return length;
+
+cleanup:
+ kfree(dev->policy_buf);
+ dev->policy_buf = NULL;
+ return ret;
}
static const struct file_operations pb_fops = {
@@ -348,12 +410,12 @@ static int amd_pmf_amdtee_ta_match(struct tee_ioctl_version_data *ver, const voi
return ver->impl_id == TEE_IMPL_ID_AMDTEE;
}
-static int amd_pmf_ta_open_session(struct tee_context *ctx, u32 *id)
+static int amd_pmf_ta_open_session(struct tee_context *ctx, u32 *id, const uuid_t *uuid)
{
struct tee_ioctl_open_session_arg sess_arg = {};
int rc;
- export_uuid(sess_arg.uuid, &amd_pmf_ta_uuid);
+ export_uuid(sess_arg.uuid, uuid);
sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
sess_arg.num_params = 0;
@@ -368,7 +430,31 @@ static int amd_pmf_ta_open_session(struct tee_context *ctx, u32 *id)
return rc;
}
-static int amd_pmf_tee_init(struct amd_pmf_dev *dev)
+static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
+{
+ int err;
+
+ dev->pmf_idev = devm_input_allocate_device(dev->dev);
+ if (!dev->pmf_idev)
+ return -ENOMEM;
+
+ dev->pmf_idev->name = "PMF-TA output events";
+ dev->pmf_idev->phys = "amd-pmf/input0";
+
+ input_set_capability(dev->pmf_idev, EV_KEY, KEY_SLEEP);
+ input_set_capability(dev->pmf_idev, EV_KEY, KEY_SCREENLOCK);
+ input_set_capability(dev->pmf_idev, EV_KEY, KEY_SUSPEND);
+
+ err = input_register_device(dev->pmf_idev);
+ if (err) {
+ dev_err(dev->dev, "Failed to register input device: %d\n", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
{
u32 size;
int ret;
@@ -379,7 +465,7 @@ static int amd_pmf_tee_init(struct amd_pmf_dev *dev)
return PTR_ERR(dev->tee_ctx);
}
- ret = amd_pmf_ta_open_session(dev->tee_ctx, &dev->session_id);
+ ret = amd_pmf_ta_open_session(dev->tee_ctx, &dev->session_id, uuid);
if (ret) {
dev_err(dev->dev, "Failed to open TA session (%d)\n", ret);
ret = -EINVAL;
@@ -423,7 +509,8 @@ static void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
{
- int ret;
+ bool status;
+ int ret, i;
ret = apmf_check_smart_pc(dev);
if (ret) {
@@ -436,55 +523,100 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
return -ENODEV;
}
- ret = amd_pmf_tee_init(dev);
- if (ret)
- return ret;
-
INIT_DELAYED_WORK(&dev->pb_work, amd_pmf_invoke_cmd);
ret = amd_pmf_set_dram_addr(dev, true);
if (ret)
- goto error;
+ goto err_cancel_work;
- dev->policy_base = devm_ioremap(dev->dev, dev->policy_addr, dev->policy_sz);
- if (!dev->policy_base) {
- ret = -ENOMEM;
- goto error;
+ dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
+ if (IS_ERR(dev->policy_base)) {
+ ret = PTR_ERR(dev->policy_base);
+ goto err_free_dram_buf;
}
dev->policy_buf = kzalloc(dev->policy_sz, GFP_KERNEL);
if (!dev->policy_buf) {
ret = -ENOMEM;
- goto error;
+ goto err_free_dram_buf;
}
memcpy_fromio(dev->policy_buf, dev->policy_base, dev->policy_sz);
+ if (!amd_pmf_pb_valid(dev)) {
+ dev_info(dev->dev, "No Smart PC policy present\n");
+ ret = -EINVAL;
+ goto err_free_policy;
+ }
+
amd_pmf_hex_dump_pb(dev);
dev->prev_data = kzalloc(sizeof(*dev->prev_data), GFP_KERNEL);
if (!dev->prev_data) {
ret = -ENOMEM;
- goto error;
+ goto err_free_policy;
}
- ret = amd_pmf_start_policy_engine(dev);
- if (ret)
- goto error;
+ for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
+ ret = amd_pmf_tee_init(dev, &amd_pmf_ta_uuid[i]);
+ if (ret)
+ goto err_free_prev_data;
+
+ ret = amd_pmf_start_policy_engine(dev);
+ switch (ret) {
+ case TA_PMF_TYPE_SUCCESS:
+ status = true;
+ break;
+ case TA_ERROR_CRYPTO_INVALID_PARAM:
+ case TA_ERROR_CRYPTO_BIN_TOO_LARGE:
+ amd_pmf_tee_deinit(dev);
+ status = false;
+ break;
+ default:
+ ret = -EINVAL;
+ amd_pmf_tee_deinit(dev);
+ goto err_free_prev_data;
+ }
+
+ if (status)
+ break;
+ }
+
+ if (!status && !pb_side_load) {
+ ret = -EINVAL;
+ goto err_free_prev_data;
+ }
if (pb_side_load)
amd_pmf_open_pb(dev, dev->dbgfs_dir);
+ ret = amd_pmf_register_input_device(dev);
+ if (ret)
+ goto err_pmf_remove_pb;
+
return 0;
-error:
- amd_pmf_deinit_smart_pc(dev);
+err_pmf_remove_pb:
+ if (pb_side_load && dev->esbin)
+ amd_pmf_remove_pb(dev);
+ amd_pmf_tee_deinit(dev);
+err_free_prev_data:
+ kfree(dev->prev_data);
+err_free_policy:
+ kfree(dev->policy_buf);
+err_free_dram_buf:
+ kfree(dev->buf);
+err_cancel_work:
+ cancel_delayed_work_sync(&dev->pb_work);
return ret;
}
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev)
{
+ if (dev->pmf_idev)
+ input_unregister_device(dev->pmf_idev);
+
if (pb_side_load && dev->esbin)
amd_pmf_remove_pb(dev);
diff --git a/drivers/platform/x86/amd/x3d_vcache.c b/drivers/platform/x86/amd/x3d_vcache.c
new file mode 100644
index 000000000000..0f6d3c54d879
--- /dev/null
+++ b/drivers/platform/x86/amd/x3d_vcache.c
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD 3D V-Cache Performance Optimizer Driver
+ *
+ * Copyright (c) 2024, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Authors: Basavaraj Natikar <Basavaraj.Natikar@amd.com>
+ * Perry Yuan <perry.yuan@amd.com>
+ * Mario Limonciello <mario.limonciello@amd.com>
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/sysfs.h>
+#include <linux/uuid.h>
+
+static char *x3d_mode = "frequency";
+module_param(x3d_mode, charp, 0);
+MODULE_PARM_DESC(x3d_mode, "Initial 3D-VCache mode; 'frequency' (default) or 'cache'");
+
+#define DSM_REVISION_ID 0
+#define DSM_SET_X3D_MODE 1
+
+static guid_t x3d_guid = GUID_INIT(0xdff8e55f, 0xbcfd, 0x46fb, 0xba, 0x0a,
+ 0xef, 0xd0, 0x45, 0x0f, 0x34, 0xee);
+
+enum amd_x3d_mode_type {
+ MODE_INDEX_FREQ,
+ MODE_INDEX_CACHE,
+};
+
+static const char * const amd_x3d_mode_strings[] = {
+ [MODE_INDEX_FREQ] = "frequency",
+ [MODE_INDEX_CACHE] = "cache",
+};
+
+struct amd_x3d_dev {
+ struct device *dev;
+ acpi_handle ahandle;
+ /* To protect x3d mode setting */
+ struct mutex lock;
+ enum amd_x3d_mode_type curr_mode;
+};
+
+static int amd_x3d_get_mode(struct amd_x3d_dev *data)
+{
+ guard(mutex)(&data->lock);
+
+ return data->curr_mode;
+}
+
+static int amd_x3d_mode_switch(struct amd_x3d_dev *data, int new_state)
+{
+ union acpi_object *out, argv;
+
+ guard(mutex)(&data->lock);
+ argv.type = ACPI_TYPE_INTEGER;
+ argv.integer.value = new_state;
+
+ out = acpi_evaluate_dsm(data->ahandle, &x3d_guid, DSM_REVISION_ID,
+ DSM_SET_X3D_MODE, &argv);
+ if (!out) {
+ dev_err(data->dev, "failed to evaluate _DSM\n");
+ return -EINVAL;
+ }
+
+ data->curr_mode = new_state;
+
+ kfree(out);
+
+ return 0;
+}
+
+static ssize_t amd_x3d_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct amd_x3d_dev *data = dev_get_drvdata(dev);
+ int ret;
+
+ ret = sysfs_match_string(amd_x3d_mode_strings, buf);
+ if (ret < 0)
+ return ret;
+
+ ret = amd_x3d_mode_switch(data, ret);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t amd_x3d_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct amd_x3d_dev *data = dev_get_drvdata(dev);
+ int mode = amd_x3d_get_mode(data);
+
+ return sysfs_emit(buf, "%s\n", amd_x3d_mode_strings[mode]);
+}
+static DEVICE_ATTR_RW(amd_x3d_mode);
+
+static struct attribute *amd_x3d_attrs[] = {
+ &dev_attr_amd_x3d_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(amd_x3d);
+
+static int amd_x3d_resume_handler(struct device *dev)
+{
+ struct amd_x3d_dev *data = dev_get_drvdata(dev);
+ int ret = amd_x3d_get_mode(data);
+
+ return amd_x3d_mode_switch(data, ret);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(amd_x3d_pm, NULL, amd_x3d_resume_handler);
+
+static const struct acpi_device_id amd_x3d_acpi_ids[] = {
+ {"AMDI0101"},
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, amd_x3d_acpi_ids);
+
+static int amd_x3d_probe(struct platform_device *pdev)
+{
+ struct amd_x3d_dev *data;
+ acpi_handle handle;
+ int ret;
+
+ handle = ACPI_HANDLE(&pdev->dev);
+ if (!handle)
+ return -ENODEV;
+
+ if (!acpi_check_dsm(handle, &x3d_guid, DSM_REVISION_ID, BIT(DSM_SET_X3D_MODE)))
+ return -ENODEV;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->dev = &pdev->dev;
+
+ ret = devm_mutex_init(data->dev, &data->lock);
+ if (ret)
+ return ret;
+
+ data->ahandle = handle;
+ platform_set_drvdata(pdev, data);
+
+ ret = match_string(amd_x3d_mode_strings, ARRAY_SIZE(amd_x3d_mode_strings), x3d_mode);
+ if (ret < 0)
+ return dev_err_probe(&pdev->dev, -EINVAL, "invalid mode %s\n", x3d_mode);
+
+ return amd_x3d_mode_switch(data, ret);
+}
+
+static struct platform_driver amd_3d_vcache_driver = {
+ .driver = {
+ .name = "amd_x3d_vcache",
+ .dev_groups = amd_x3d_groups,
+ .acpi_match_table = amd_x3d_acpi_ids,
+ .pm = pm_sleep_ptr(&amd_x3d_pm),
+ },
+ .probe = amd_x3d_probe,
+};
+module_platform_driver(amd_3d_vcache_driver);
+
+MODULE_DESCRIPTION("AMD 3D V-Cache Performance Optimizer Driver");
+MODULE_LICENSE("GPL");