aboutsummaryrefslogtreecommitdiff
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/Kconfig18
-rw-r--r--drivers/platform/x86/amd/Makefile5
-rw-r--r--drivers/platform/x86/amd/hsmp.c988
-rw-r--r--drivers/platform/x86/amd/hsmp/Kconfig47
-rw-r--r--drivers/platform/x86/amd/hsmp/Makefile12
-rw-r--r--drivers/platform/x86/amd/hsmp/acpi.c378
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.c408
-rw-r--r--drivers/platform/x86/amd/hsmp/hsmp.h66
-rw-r--r--drivers/platform/x86/amd/hsmp/plat.c338
-rw-r--r--drivers/platform/x86/amd/pmc/pmc.c2
-rw-r--r--drivers/platform/x86/amd/pmf/Kconfig1
-rw-r--r--drivers/platform/x86/amd/pmf/acpi.c46
-rw-r--r--drivers/platform/x86/amd/pmf/core.c10
-rw-r--r--drivers/platform/x86/amd/pmf/pmf.h6
-rw-r--r--drivers/platform/x86/amd/pmf/tee-if.c8
-rw-r--r--drivers/platform/x86/amd/x3d_vcache.c176
16 files changed, 1471 insertions, 1038 deletions
diff --git a/drivers/platform/x86/amd/Kconfig b/drivers/platform/x86/amd/Kconfig
index f88682d36447..c3e086ea64fc 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)"
diff --git a/drivers/platform/x86/amd/Makefile b/drivers/platform/x86/amd/Makefile
index dcec0a46f8af..56f62fc9c97b 100644
--- a/drivers/platform/x86/amd/Makefile
+++ b/drivers/platform/x86/amd/Makefile
@@ -4,8 +4,9 @@
# AMD x86 Platform-Specific Drivers
#
+obj-$(CONFIG_AMD_3D_VCACHE) += amd_3d_vcache.o
+amd_3d_vcache-objs := 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
diff --git a/drivers/platform/x86/amd/hsmp.c b/drivers/platform/x86/amd/hsmp.c
deleted file mode 100644
index 8fcf38eed7f0..000000000000
--- a/drivers/platform/x86/amd/hsmp.c
+++ /dev/null
@@ -1,988 +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 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;
-}
-
-/*
- * 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 ... 0x1F:
- return true;
- default:
- return false;
- }
- default:
- return false;
- }
-
- return false;
-}
-
-static int __init hsmp_plt_init(void)
-{
- int ret = -ENODEV;
-
- /*
- * 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) {
- if (legacy_hsmp_support()) {
- /* Not ACPI device, but supports HSMP, register a plat_dev */
- ret = hsmp_plat_dev_register();
- } else {
- /* Not ACPI, Does not support HSMP */
- pr_info("HSMP is not supported on Family:%x model:%x\n",
- boot_cpu_data.x86, boot_cpu_data.x86_model);
- ret = -ENODEV;
- }
- 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..7d10d4462a45
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/Kconfig
@@ -0,0 +1,47 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD HSMP Driver
+#
+
+config AMD_HSMP
+ tristate
+
+menu "AMD HSMP Driver"
+ depends on AMD_NB || COMPILE_TEST
+
+config AMD_HSMP_ACPI
+ tristate "AMD HSMP ACPI device driver"
+ depends on ACPI
+ 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"
+ 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..3175d8885e87
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/Makefile
@@ -0,0 +1,12 @@
+# 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-objs := hsmp.o
+obj-$(CONFIG_AMD_HSMP_PLAT) += amd_hsmp.o
+amd_hsmp-objs := plat.o
+obj-$(CONFIG_AMD_HSMP_ACPI) += hsmp_acpi.o
+hsmp_acpi-objs := 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..4aa4d66f491a
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -0,0 +1,378 @@
+// 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 <asm/amd_nb.h>
+
+#include <linux/acpi.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 "hsmp.h"
+
+#define DRIVER_NAME "amd_hsmp"
+#define DRIVER_VERSION "2.3"
+#define ACPI_HSMP_DEVICE_HID "AMDI0097"
+
+/* 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;
+
+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,
+ 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,
+ struct bin_attribute *battr, int id)
+{
+ if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6)
+ return battr->attr.mode;
+
+ return 0;
+}
+
+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");
+ }
+
+ return ret;
+}
+
+static struct bin_attribute hsmp_metric_tbl_attr = {
+ .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444},
+ .read = hsmp_metric_tbl_acpi_read,
+ .size = sizeof(struct hsmp_metric_table),
+};
+
+static struct bin_attribute *hsmp_attr_list[] = {
+ &hsmp_metric_tbl_attr,
+ NULL
+};
+
+static struct attribute_group hsmp_attr_grp = {
+ .bin_attrs = hsmp_attr_list,
+ .is_bin_visible = hsmp_is_sock_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_nb_num();
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
+ 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..f29dd93fbf0b
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -0,0 +1,408 @@
+// 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 <asm/amd_nb.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
+
+#define DRIVER_VERSION "2.3"
+
+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 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 (!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_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);
+
+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 -EPERM;
+ 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 -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..e852f0a947e4
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -0,0 +1,66 @@
+/* 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/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 MAX_AMD_SOCKETS 8
+
+#define HSMP_CDEV_NAME "hsmp_cdev"
+#define HSMP_DEVNODE_NAME "hsmp"
+
+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;
+ 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);
+#endif /* HSMP_H */
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
new file mode 100644
index 000000000000..f8e74c0392ba
--- /dev/null
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -0,0 +1,338 @@
+// 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 <asm/amd_nb.h>
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/sysfs.h>
+
+#include "hsmp.h"
+
+#define DRIVER_NAME "amd_hsmp"
+#define DRIVER_VERSION "2.3"
+
+/*
+ * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox
+ * 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
+
+static struct hsmp_plat_device *hsmp_pdev;
+
+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 ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj,
+ 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,
+ 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.
+ */
+#define HSMP_BIN_ATTR(index, _list) \
+static struct bin_attribute attr##index = { \
+ .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \
+ .private = (void *)index, \
+ .read = hsmp_metric_tbl_plat_read, \
+ .size = sizeof(struct hsmp_metric_table), \
+}; \
+static 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 struct attribute_group sock##index##_attr_grp = { \
+ .bin_attrs = _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++) {
+ if (!node_to_amd_nb(i))
+ return -ENODEV;
+ sock = &hsmp_pdev->sock[i];
+ sock->root = node_to_amd_nb(i)->root;
+ 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");
+ }
+ }
+
+ 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 ... 0x1F:
+ 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;
+ }
+
+ hsmp_pdev = get_hsmp_pdev();
+ if (!hsmp_pdev)
+ return -ENOMEM;
+
+ /*
+ * 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
+ */
+ hsmp_pdev->num_sockets = amd_nb_num();
+ if (hsmp_pdev->num_sockets == 0 || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS)
+ 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/pmc.c b/drivers/platform/x86/amd/pmc/pmc.c
index 5669f94c3d06..26b878ee5191 100644
--- a/drivers/platform/x86/amd/pmc/pmc.c
+++ b/drivers/platform/x86/amd/pmc/pmc.c
@@ -1161,7 +1161,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/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
index f4fa8bd8bda8..99d67cdbd91e 100644
--- a/drivers/platform/x86/amd/pmf/Kconfig
+++ b/drivers/platform/x86/amd/pmf/Kconfig
@@ -11,6 +11,7 @@ config AMD_PMF
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/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index d5b496433d69..1b9c7acf0ddf 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -433,37 +433,29 @@ int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
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;
- }
-
- 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->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;
}
- return AE_OK;
-}
-
-int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
-{
- 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);
+ 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;
}
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index 347bb43a5f2b..06a97c533cb8 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -430,18 +430,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;
@@ -497,7 +497,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.h b/drivers/platform/x86/amd/pmf/pmf.h
index 8ce8816da9c1..a79808fda1d8 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -13,6 +13,7 @@
#include <linux/acpi.h>
#include <linux/input.h>
+#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#define POLICY_BUF_MAX_SZ 0x4b000
@@ -355,19 +356,20 @@ 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_sps_prop_granular_v2 {
diff --git a/drivers/platform/x86/amd/pmf/tee-if.c b/drivers/platform/x86/amd/pmf/tee-if.c
index 19c27b6e4666..8c88769ea1d8 100644
--- a/drivers/platform/x86/amd/pmf/tee-if.c
+++ b/drivers/platform/x86/amd/pmf/tee-if.c
@@ -257,7 +257,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;
@@ -512,9 +512,9 @@ int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
if (ret)
goto error;
- dev->policy_base = devm_ioremap(dev->dev, dev->policy_addr, dev->policy_sz);
- if (!dev->policy_base) {
- ret = -ENOMEM;
+ dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
+ if (IS_ERR(dev->policy_base)) {
+ ret = PTR_ERR(dev->policy_base);
goto error;
}
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");