platform/x86/amd/pmf: Add support for PMF core layer
authorShyam Sundar S K <Shyam-sundar.S-k@amd.com>
Tue, 2 Aug 2022 15:11:39 +0000 (20:41 +0530)
committerHans de Goede <hdegoede@redhat.com>
Mon, 15 Aug 2022 11:23:37 +0000 (13:23 +0200)
PMF core layer is meant to abstract the common functionalities
across PMF features. This layer also does the plumbing work
like setting up the mailbox channel for the communication
between the PMF driver and the PMFW (Power Management Firmware)
running on the SMU.

Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
Link: https://lore.kernel.org/r/20220802151149.2123699-2-Shyam-sundar.S-k@amd.com
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
drivers/platform/x86/amd/Kconfig
drivers/platform/x86/amd/Makefile
drivers/platform/x86/amd/pmf/Kconfig [new file with mode: 0644]
drivers/platform/x86/amd/pmf/Makefile [new file with mode: 0644]
drivers/platform/x86/amd/pmf/core.c [new file with mode: 0644]
drivers/platform/x86/amd/pmf/pmf.h [new file with mode: 0644]

index c0d0a3c..a825af8 100644 (file)
@@ -3,6 +3,8 @@
 # AMD x86 Platform Specific Drivers
 #
 
+source "drivers/platform/x86/amd/pmf/Kconfig"
+
 config AMD_PMC
        tristate "AMD SoC PMC driver"
        depends on ACPI && PCI && RTC_CLASS
index a03fbb0..2c22919 100644 (file)
@@ -8,3 +8,4 @@ amd-pmc-y                       := pmc.o
 obj-$(CONFIG_AMD_PMC)          += amd-pmc.o
 amd_hsmp-y                     := hsmp.o
 obj-$(CONFIG_AMD_HSMP)         += amd_hsmp.o
+obj-$(CONFIG_AMD_PMF)          += pmf/
diff --git a/drivers/platform/x86/amd/pmf/Kconfig b/drivers/platform/x86/amd/pmf/Kconfig
new file mode 100644 (file)
index 0000000..e65ffa5
--- /dev/null
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# AMD PMF Driver
+#
+
+config AMD_PMF
+       tristate "AMD Platform Management Framework"
+       depends on ACPI && PCI
+       help
+         This driver provides support for the AMD Platform Management Framework.
+         The goal is to enhance end user experience by making AMD PCs smarter,
+         quiter, power efficient by adapting to user behavior and environment.
+
+         To compile this driver as a module, choose M here: the module will
+         be called amd_pmf.
diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
new file mode 100644 (file)
index 0000000..459005f
--- /dev/null
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/amd/pmf
+# AMD Platform Management Framework
+#
+
+obj-$(CONFIG_AMD_PMF) += amd-pmf.o
+amd-pmf-objs := core.o
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
new file mode 100644 (file)
index 0000000..aef9796
--- /dev/null
@@ -0,0 +1,235 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include "pmf.h"
+
+/* PMF-SMU communication registers */
+#define AMD_PMF_REGISTER_MESSAGE       0xA18
+#define AMD_PMF_REGISTER_RESPONSE      0xA78
+#define AMD_PMF_REGISTER_ARGUMENT      0xA58
+
+/* Base address of SMU for mapping physical address to virtual address */
+#define AMD_PMF_SMU_INDEX_ADDRESS      0xB8
+#define AMD_PMF_SMU_INDEX_DATA         0xBC
+#define AMD_PMF_MAPPING_SIZE           0x01000
+#define AMD_PMF_BASE_ADDR_OFFSET       0x10000
+#define AMD_PMF_BASE_ADDR_LO           0x13B102E8
+#define AMD_PMF_BASE_ADDR_HI           0x13B102EC
+#define AMD_PMF_BASE_ADDR_LO_MASK      GENMASK(15, 0)
+#define AMD_PMF_BASE_ADDR_HI_MASK      GENMASK(31, 20)
+
+/* SMU Response Codes */
+#define AMD_PMF_RESULT_OK                    0x01
+#define AMD_PMF_RESULT_CMD_REJECT_BUSY       0xFC
+#define AMD_PMF_RESULT_CMD_REJECT_PREREQ     0xFD
+#define AMD_PMF_RESULT_CMD_UNKNOWN           0xFE
+#define AMD_PMF_RESULT_FAILED                0xFF
+
+/* List of supported CPU ids */
+#define AMD_CPU_ID_PS                  0x14e8
+
+#define PMF_MSG_DELAY_MIN_US           50
+#define RESPONSE_REGISTER_LOOP_MAX     20000
+
+#define DELAY_MIN_US   2000
+#define DELAY_MAX_US   3000
+
+static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
+{
+       return ioread32(dev->regbase + reg_offset);
+}
+
+static inline void amd_pmf_reg_write(struct amd_pmf_dev *dev, int reg_offset, u32 val)
+{
+       iowrite32(val, dev->regbase + reg_offset);
+}
+
+static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
+{
+       u32 value;
+
+       value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE);
+       dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
+
+       value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+       dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
+
+       value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
+       dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
+}
+
+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);
+
+       /* Wait until we get a valid response */
+       rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+                               val, val != 0, PMF_MSG_DELAY_MIN_US,
+                               PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
+       if (rc) {
+               dev_err(dev->dev, "failed to talk to SMU\n");
+               goto out_unlock;
+       }
+
+       /* Write zero to response register */
+       amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
+
+       /* Write argument into argument register */
+       amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
+
+       /* Write message ID to message ID register */
+       amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
+
+       /* Wait until we get a valid response */
+       rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
+                               val, val != 0, PMF_MSG_DELAY_MIN_US,
+                               PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
+       if (rc) {
+               dev_err(dev->dev, "SMU response timed out\n");
+               goto out_unlock;
+       }
+
+       switch (val) {
+       case AMD_PMF_RESULT_OK:
+               if (get) {
+                       /* PMFW may take longer time to return back the data */
+                       usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
+                       *data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
+               }
+               break;
+       case AMD_PMF_RESULT_CMD_REJECT_BUSY:
+               dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val);
+               rc = -EBUSY;
+               goto out_unlock;
+       case AMD_PMF_RESULT_CMD_UNKNOWN:
+               dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val);
+               rc = -EINVAL;
+               goto out_unlock;
+       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;
+       }
+
+out_unlock:
+       mutex_unlock(&dev->lock);
+       amd_pmf_dump_registers(dev);
+       return rc;
+}
+
+static const struct pci_device_id pmf_pci_ids[] = {
+       { PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
+       { }
+};
+
+static const struct acpi_device_id amd_pmf_acpi_ids[] = {
+       {"AMDI0102", 0},
+       { }
+};
+MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
+
+static int amd_pmf_probe(struct platform_device *pdev)
+{
+       struct amd_pmf_dev *dev;
+       struct pci_dev *rdev;
+       u32 base_addr_lo;
+       u32 base_addr_hi;
+       u64 base_addr;
+       u32 val;
+       int err;
+
+       dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
+       if (!dev)
+               return -ENOMEM;
+
+       dev->dev = &pdev->dev;
+
+       rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
+       if (!rdev || !pci_match_id(pmf_pci_ids, rdev)) {
+               pci_dev_put(rdev);
+               return -ENODEV;
+       }
+
+       dev->cpu_id = rdev->device;
+       err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_LO);
+       if (err) {
+               dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
+               pci_dev_put(rdev);
+               return pcibios_err_to_errno(err);
+       }
+
+       err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
+       if (err) {
+               pci_dev_put(rdev);
+               return pcibios_err_to_errno(err);
+       }
+
+       base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
+
+       err = pci_write_config_dword(rdev, AMD_PMF_SMU_INDEX_ADDRESS, AMD_PMF_BASE_ADDR_HI);
+       if (err) {
+               dev_err(dev->dev, "error writing to 0x%x\n", AMD_PMF_SMU_INDEX_ADDRESS);
+               pci_dev_put(rdev);
+               return pcibios_err_to_errno(err);
+       }
+
+       err = pci_read_config_dword(rdev, AMD_PMF_SMU_INDEX_DATA, &val);
+       if (err) {
+               pci_dev_put(rdev);
+               return pcibios_err_to_errno(err);
+       }
+
+       base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
+       pci_dev_put(rdev);
+       base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
+
+       dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMF_BASE_ADDR_OFFSET,
+                                   AMD_PMF_MAPPING_SIZE);
+       if (!dev->regbase)
+               return -ENOMEM;
+
+       platform_set_drvdata(pdev, dev);
+
+       mutex_init(&dev->lock);
+       dev_info(dev->dev, "registered PMF device successfully\n");
+
+       return 0;
+}
+
+static int amd_pmf_remove(struct platform_device *pdev)
+{
+       struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
+
+       mutex_destroy(&dev->lock);
+       kfree(dev->buf);
+       return 0;
+}
+
+static struct platform_driver amd_pmf_driver = {
+       .driver = {
+               .name = "amd-pmf",
+               .acpi_match_table = amd_pmf_acpi_ids,
+       },
+       .probe = amd_pmf_probe,
+       .remove = amd_pmf_remove,
+};
+module_platform_driver(amd_pmf_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("AMD Platform Management Framework Driver");
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
new file mode 100644 (file)
index 0000000..1c2e942
--- /dev/null
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * AMD Platform Management Framework Driver
+ *
+ * Copyright (c) 2022, Advanced Micro Devices, Inc.
+ * All Rights Reserved.
+ *
+ * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
+ */
+
+#ifndef PMF_H
+#define PMF_H
+
+/* Message Definitions */
+#define SET_SPL                                0x03 /* SPL: Sustained Power Limit */
+#define SET_SPPT                       0x05 /* SPPT: Slow Package Power Tracking */
+#define SET_FPPT                       0x07 /* FPPT: Fast Package Power Tracking */
+#define GET_SPL                                0x0B
+#define GET_SPPT                       0x0D
+#define GET_FPPT                       0x0F
+#define SET_DRAM_ADDR_HIGH     0x14
+#define SET_DRAM_ADDR_LOW      0x15
+#define SET_TRANSFER_TABLE     0x16
+#define SET_STT_MIN_LIMIT      0x18 /* STT: Skin Temperature Tracking */
+#define SET_STT_LIMIT_APU      0x19
+#define SET_STT_LIMIT_HS2      0x1A
+#define SET_SPPT_APU_ONLY      0x1D
+#define GET_SPPT_APU_ONLY      0x1E
+#define GET_STT_MIN_LIMIT      0x1F
+#define GET_STT_LIMIT_APU      0x20
+#define GET_STT_LIMIT_HS2      0x21
+
+struct amd_pmf_dev {
+       void __iomem *regbase;
+       void __iomem *smu_virt_addr;
+       void *buf;
+       u32 base_addr;
+       u32 cpu_id;
+       struct device *dev;
+       struct mutex lock; /* protects the PMF interface */
+};
+
+/* Core Layer */
+int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data);
+
+#endif /* PMF_H */