summaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/intel/speed_select_if
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:27:49 +0000
commitace9429bb58fd418f0c81d4c2835699bddf6bde6 (patch)
treeb2d64bc10158fdd5497876388cd68142ca374ed3 /drivers/platform/x86/intel/speed_select_if
parentInitial commit. (diff)
downloadlinux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.tar.xz
linux-ace9429bb58fd418f0c81d4c2835699bddf6bde6.zip
Adding upstream version 6.6.15.upstream/6.6.15
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'drivers/platform/x86/intel/speed_select_if')
-rw-r--r--drivers/platform/x86/intel/speed_select_if/Kconfig21
-rw-r--r--drivers/platform/x86/intel/speed_select_if/Makefile12
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.c840
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_common.h79
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c214
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c227
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c200
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi.c72
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c1442
-rw-r--r--drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.h18
10 files changed, 3125 insertions, 0 deletions
diff --git a/drivers/platform/x86/intel/speed_select_if/Kconfig b/drivers/platform/x86/intel/speed_select_if/Kconfig
new file mode 100644
index 0000000000..4eb3ad299d
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/Kconfig
@@ -0,0 +1,21 @@
+menu "Intel Speed Select Technology interface support"
+ depends on PCI
+ depends on X86_64 || COMPILE_TEST
+
+config INTEL_SPEED_SELECT_TPMI
+ tristate
+
+config INTEL_SPEED_SELECT_INTERFACE
+ tristate "Intel(R) Speed Select Technology interface drivers"
+ select INTEL_SPEED_SELECT_TPMI if INTEL_TPMI
+ help
+ This config enables the Intel(R) Speed Select Technology interface
+ drivers. The Intel(R) speed select technology features are non
+ architectural and only supported on specific Xeon(R) servers.
+ These drivers provide interface to directly communicate with hardware
+ via MMIO and Mail boxes to enumerate and control all the speed select
+ features.
+
+ Enable this config, if there is a need to enable and control the
+ Intel(R) Speed Select Technology features from the user space.
+endmenu
diff --git a/drivers/platform/x86/intel/speed_select_if/Makefile b/drivers/platform/x86/intel/speed_select_if/Makefile
new file mode 100644
index 0000000000..1d878a36d0
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile - Intel Speed Select Interface drivers
+# Copyright (c) 2019, Intel Corporation.
+#
+
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_common.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mmio.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_pci.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += isst_if_mbox_msr.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_TPMI) += isst_tpmi_core.o
+obj-$(CONFIG_INTEL_SPEED_SELECT_TPMI) += isst_tpmi.o
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.c b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
new file mode 100644
index 0000000000..08df949460
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.c
@@ -0,0 +1,840 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Common functions
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpufeature.h>
+#include <linux/cpuhotplug.h>
+#include <linux/fs.h>
+#include <linux/hashtable.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#include "isst_if_common.h"
+
+#define MSR_THREAD_ID_INFO 0x53
+#define MSR_PM_LOGICAL_ID 0x54
+#define MSR_CPU_BUS_NUMBER 0x128
+
+static struct isst_if_cmd_cb punit_callbacks[ISST_IF_DEV_MAX];
+
+static int punit_msr_white_list[] = {
+ MSR_TURBO_RATIO_LIMIT,
+ MSR_CONFIG_TDP_CONTROL,
+ MSR_TURBO_RATIO_LIMIT1,
+ MSR_TURBO_RATIO_LIMIT2,
+ MSR_PM_LOGICAL_ID,
+};
+
+struct isst_valid_cmd_ranges {
+ u16 cmd;
+ u16 sub_cmd_beg;
+ u16 sub_cmd_end;
+};
+
+struct isst_cmd_set_req_type {
+ u16 cmd;
+ u16 sub_cmd;
+ u16 param;
+};
+
+static const struct isst_valid_cmd_ranges isst_valid_cmds[] = {
+ {0xD0, 0x00, 0x03},
+ {0x7F, 0x00, 0x0C},
+ {0x7F, 0x10, 0x12},
+ {0x7F, 0x20, 0x23},
+ {0x94, 0x03, 0x03},
+ {0x95, 0x03, 0x03},
+};
+
+static const struct isst_cmd_set_req_type isst_cmd_set_reqs[] = {
+ {0xD0, 0x00, 0x08},
+ {0xD0, 0x01, 0x08},
+ {0xD0, 0x02, 0x08},
+ {0xD0, 0x03, 0x08},
+ {0x7F, 0x02, 0x00},
+ {0x7F, 0x08, 0x00},
+ {0x95, 0x03, 0x03},
+};
+
+struct isst_cmd {
+ struct hlist_node hnode;
+ u64 data;
+ u32 cmd;
+ int cpu;
+ int mbox_cmd_type;
+ u32 param;
+};
+
+static bool isst_hpm_support;
+
+static DECLARE_HASHTABLE(isst_hash, 8);
+static DEFINE_MUTEX(isst_hash_lock);
+
+static int isst_store_new_cmd(int cmd, u32 cpu, int mbox_cmd_type, u32 param,
+ u32 data)
+{
+ struct isst_cmd *sst_cmd;
+
+ sst_cmd = kmalloc(sizeof(*sst_cmd), GFP_KERNEL);
+ if (!sst_cmd)
+ return -ENOMEM;
+
+ sst_cmd->cpu = cpu;
+ sst_cmd->cmd = cmd;
+ sst_cmd->mbox_cmd_type = mbox_cmd_type;
+ sst_cmd->param = param;
+ sst_cmd->data = data;
+
+ hash_add(isst_hash, &sst_cmd->hnode, sst_cmd->cmd);
+
+ return 0;
+}
+
+static void isst_delete_hash(void)
+{
+ struct isst_cmd *sst_cmd;
+ struct hlist_node *tmp;
+ int i;
+
+ hash_for_each_safe(isst_hash, i, tmp, sst_cmd, hnode) {
+ hash_del(&sst_cmd->hnode);
+ kfree(sst_cmd);
+ }
+}
+
+/**
+ * isst_store_cmd() - Store command to a hash table
+ * @cmd: Mailbox command.
+ * @sub_cmd: Mailbox sub-command or MSR id.
+ * @cpu: Target CPU for the command
+ * @mbox_cmd_type: Mailbox or MSR command.
+ * @param: Mailbox parameter.
+ * @data: Mailbox request data or MSR data.
+ *
+ * Stores the command to a hash table if there is no such command already
+ * stored. If already stored update the latest parameter and data for the
+ * command.
+ *
+ * Return: Return result of store to hash table, 0 for success, others for
+ * failure.
+ */
+int isst_store_cmd(int cmd, int sub_cmd, u32 cpu, int mbox_cmd_type,
+ u32 param, u64 data)
+{
+ struct isst_cmd *sst_cmd;
+ int full_cmd, ret;
+
+ full_cmd = (cmd & GENMASK_ULL(15, 0)) << 16;
+ full_cmd |= (sub_cmd & GENMASK_ULL(15, 0));
+ mutex_lock(&isst_hash_lock);
+ hash_for_each_possible(isst_hash, sst_cmd, hnode, full_cmd) {
+ if (sst_cmd->cmd == full_cmd && sst_cmd->cpu == cpu &&
+ sst_cmd->mbox_cmd_type == mbox_cmd_type) {
+ sst_cmd->param = param;
+ sst_cmd->data = data;
+ mutex_unlock(&isst_hash_lock);
+ return 0;
+ }
+ }
+
+ ret = isst_store_new_cmd(full_cmd, cpu, mbox_cmd_type, param, data);
+ mutex_unlock(&isst_hash_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(isst_store_cmd);
+
+static void isst_mbox_resume_command(struct isst_if_cmd_cb *cb,
+ struct isst_cmd *sst_cmd)
+{
+ struct isst_if_mbox_cmd mbox_cmd;
+ int wr_only;
+
+ mbox_cmd.command = (sst_cmd->cmd & GENMASK_ULL(31, 16)) >> 16;
+ mbox_cmd.sub_command = sst_cmd->cmd & GENMASK_ULL(15, 0);
+ mbox_cmd.parameter = sst_cmd->param;
+ mbox_cmd.req_data = sst_cmd->data;
+ mbox_cmd.logical_cpu = sst_cmd->cpu;
+ (cb->cmd_callback)((u8 *)&mbox_cmd, &wr_only, 1);
+}
+
+/**
+ * isst_resume_common() - Process Resume request
+ *
+ * On resume replay all mailbox commands and MSRs.
+ *
+ * Return: None.
+ */
+void isst_resume_common(void)
+{
+ struct isst_cmd *sst_cmd;
+ int i;
+
+ hash_for_each(isst_hash, i, sst_cmd, hnode) {
+ struct isst_if_cmd_cb *cb;
+
+ if (sst_cmd->mbox_cmd_type) {
+ cb = &punit_callbacks[ISST_IF_DEV_MBOX];
+ if (cb->registered)
+ isst_mbox_resume_command(cb, sst_cmd);
+ } else {
+ wrmsrl_safe_on_cpu(sst_cmd->cpu, sst_cmd->cmd,
+ sst_cmd->data);
+ }
+ }
+}
+EXPORT_SYMBOL_GPL(isst_resume_common);
+
+static void isst_restore_msr_local(int cpu)
+{
+ struct isst_cmd *sst_cmd;
+ int i;
+
+ mutex_lock(&isst_hash_lock);
+ for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
+ if (!punit_msr_white_list[i])
+ break;
+
+ hash_for_each_possible(isst_hash, sst_cmd, hnode,
+ punit_msr_white_list[i]) {
+ if (!sst_cmd->mbox_cmd_type && sst_cmd->cpu == cpu)
+ wrmsrl_safe(sst_cmd->cmd, sst_cmd->data);
+ }
+ }
+ mutex_unlock(&isst_hash_lock);
+}
+
+/**
+ * isst_if_mbox_cmd_invalid() - Check invalid mailbox commands
+ * @cmd: Pointer to the command structure to verify.
+ *
+ * Invalid command to PUNIT to may result in instability of the platform.
+ * This function has a whitelist of commands, which are allowed.
+ *
+ * Return: Return true if the command is invalid, else false.
+ */
+bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd)
+{
+ int i;
+
+ if (cmd->logical_cpu >= nr_cpu_ids)
+ return true;
+
+ for (i = 0; i < ARRAY_SIZE(isst_valid_cmds); ++i) {
+ if (cmd->command == isst_valid_cmds[i].cmd &&
+ (cmd->sub_command >= isst_valid_cmds[i].sub_cmd_beg &&
+ cmd->sub_command <= isst_valid_cmds[i].sub_cmd_end)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_invalid);
+
+/**
+ * isst_if_mbox_cmd_set_req() - Check mailbox command is a set request
+ * @cmd: Pointer to the command structure to verify.
+ *
+ * Check if the given mail box level is set request and not a get request.
+ *
+ * Return: Return true if the command is set_req, else false.
+ */
+bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *cmd)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(isst_cmd_set_reqs); ++i) {
+ if (cmd->command == isst_cmd_set_reqs[i].cmd &&
+ cmd->sub_command == isst_cmd_set_reqs[i].sub_cmd &&
+ cmd->parameter == isst_cmd_set_reqs[i].param) {
+ return true;
+ }
+ }
+
+ return false;
+}
+EXPORT_SYMBOL_GPL(isst_if_mbox_cmd_set_req);
+
+static int isst_if_api_version;
+
+static int isst_if_get_platform_info(void __user *argp)
+{
+ struct isst_if_platform_info info;
+
+ info.api_version = isst_if_api_version;
+ info.driver_version = ISST_IF_DRIVER_VERSION;
+ info.max_cmds_per_ioctl = ISST_IF_CMD_LIMIT;
+ info.mbox_supported = punit_callbacks[ISST_IF_DEV_MBOX].registered;
+ info.mmio_supported = punit_callbacks[ISST_IF_DEV_MMIO].registered;
+
+ if (copy_to_user(argp, &info, sizeof(info)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define ISST_MAX_BUS_NUMBER 2
+
+struct isst_if_cpu_info {
+ /* For BUS 0 and BUS 1 only, which we need for PUNIT interface */
+ int bus_info[ISST_MAX_BUS_NUMBER];
+ struct pci_dev *pci_dev[ISST_MAX_BUS_NUMBER];
+ int punit_cpu_id;
+ int numa_node;
+};
+
+struct isst_if_pkg_info {
+ struct pci_dev *pci_dev[ISST_MAX_BUS_NUMBER];
+};
+
+static struct isst_if_cpu_info *isst_cpu_info;
+static struct isst_if_pkg_info *isst_pkg_info;
+
+static struct pci_dev *_isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
+{
+ struct pci_dev *matched_pci_dev = NULL;
+ struct pci_dev *pci_dev = NULL;
+ struct pci_dev *_pci_dev = NULL;
+ int no_matches = 0, pkg_id;
+ int bus_number;
+
+ if (bus_no < 0 || bus_no >= ISST_MAX_BUS_NUMBER || cpu < 0 ||
+ cpu >= nr_cpu_ids || cpu >= num_possible_cpus())
+ return NULL;
+
+ pkg_id = topology_physical_package_id(cpu);
+
+ bus_number = isst_cpu_info[cpu].bus_info[bus_no];
+ if (bus_number < 0)
+ return NULL;
+
+ for_each_pci_dev(_pci_dev) {
+ int node;
+
+ if (_pci_dev->bus->number != bus_number ||
+ _pci_dev->devfn != PCI_DEVFN(dev, fn))
+ continue;
+
+ ++no_matches;
+ if (!matched_pci_dev)
+ matched_pci_dev = _pci_dev;
+
+ node = dev_to_node(&_pci_dev->dev);
+ if (node == NUMA_NO_NODE) {
+ pr_info_once("Fail to get numa node for CPU:%d bus:%d dev:%d fn:%d\n",
+ cpu, bus_no, dev, fn);
+ continue;
+ }
+
+ if (node == isst_cpu_info[cpu].numa_node) {
+ isst_pkg_info[pkg_id].pci_dev[bus_no] = _pci_dev;
+
+ pci_dev = _pci_dev;
+ break;
+ }
+ }
+
+ /*
+ * If there is no numa matched pci_dev, then there can be following cases:
+ * 1. CONFIG_NUMA is not defined: In this case if there is only single device
+ * match, then we don't need numa information. Simply return last match.
+ * Othewise return NULL.
+ * 2. NUMA information is not exposed via _SEG method. In this case it is similar
+ * to case 1.
+ * 3. Numa information doesn't match with CPU numa node and more than one match
+ * return NULL.
+ */
+ if (!pci_dev && no_matches == 1)
+ pci_dev = matched_pci_dev;
+
+ /* Return pci_dev pointer for any matched CPU in the package */
+ if (!pci_dev)
+ pci_dev = isst_pkg_info[pkg_id].pci_dev[bus_no];
+
+ return pci_dev;
+}
+
+/**
+ * isst_if_get_pci_dev() - Get the PCI device instance for a CPU
+ * @cpu: Logical CPU number.
+ * @bus_no: The bus number assigned by the hardware.
+ * @dev: The device number assigned by the hardware.
+ * @fn: The function number assigned by the hardware.
+ *
+ * Using cached bus information, find out the PCI device for a bus number,
+ * device and function.
+ *
+ * Return: Return pci_dev pointer or NULL.
+ */
+struct pci_dev *isst_if_get_pci_dev(int cpu, int bus_no, int dev, int fn)
+{
+ struct pci_dev *pci_dev;
+
+ if (bus_no < 0 || bus_no >= ISST_MAX_BUS_NUMBER || cpu < 0 ||
+ cpu >= nr_cpu_ids || cpu >= num_possible_cpus())
+ return NULL;
+
+ pci_dev = isst_cpu_info[cpu].pci_dev[bus_no];
+
+ if (pci_dev && pci_dev->devfn == PCI_DEVFN(dev, fn))
+ return pci_dev;
+
+ return _isst_if_get_pci_dev(cpu, bus_no, dev, fn);
+}
+EXPORT_SYMBOL_GPL(isst_if_get_pci_dev);
+
+static int isst_if_cpu_online(unsigned int cpu)
+{
+ u64 data;
+ int ret;
+
+ isst_cpu_info[cpu].numa_node = cpu_to_node(cpu);
+
+ ret = rdmsrl_safe(MSR_CPU_BUS_NUMBER, &data);
+ if (ret) {
+ /* This is not a fatal error on MSR mailbox only I/F */
+ isst_cpu_info[cpu].bus_info[0] = -1;
+ isst_cpu_info[cpu].bus_info[1] = -1;
+ } else {
+ isst_cpu_info[cpu].bus_info[0] = data & 0xff;
+ isst_cpu_info[cpu].bus_info[1] = (data >> 8) & 0xff;
+ isst_cpu_info[cpu].pci_dev[0] = _isst_if_get_pci_dev(cpu, 0, 0, 1);
+ isst_cpu_info[cpu].pci_dev[1] = _isst_if_get_pci_dev(cpu, 1, 30, 1);
+ }
+
+ if (isst_hpm_support) {
+
+ ret = rdmsrl_safe(MSR_PM_LOGICAL_ID, &data);
+ if (!ret)
+ goto set_punit_id;
+ }
+
+ ret = rdmsrl_safe(MSR_THREAD_ID_INFO, &data);
+ if (ret) {
+ isst_cpu_info[cpu].punit_cpu_id = -1;
+ return ret;
+ }
+
+set_punit_id:
+ isst_cpu_info[cpu].punit_cpu_id = data;
+
+ isst_restore_msr_local(cpu);
+
+ return 0;
+}
+
+static int isst_if_online_id;
+
+static int isst_if_cpu_info_init(void)
+{
+ int ret;
+
+ isst_cpu_info = kcalloc(num_possible_cpus(),
+ sizeof(*isst_cpu_info),
+ GFP_KERNEL);
+ if (!isst_cpu_info)
+ return -ENOMEM;
+
+ isst_pkg_info = kcalloc(topology_max_packages(),
+ sizeof(*isst_pkg_info),
+ GFP_KERNEL);
+ if (!isst_pkg_info) {
+ kfree(isst_cpu_info);
+ return -ENOMEM;
+ }
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "platform/x86/isst-if:online",
+ isst_if_cpu_online, NULL);
+ if (ret < 0) {
+ kfree(isst_pkg_info);
+ kfree(isst_cpu_info);
+ return ret;
+ }
+
+ isst_if_online_id = ret;
+
+ return 0;
+}
+
+static void isst_if_cpu_info_exit(void)
+{
+ cpuhp_remove_state(isst_if_online_id);
+ kfree(isst_pkg_info);
+ kfree(isst_cpu_info);
+};
+
+static long isst_if_proc_phyid_req(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_cpu_map *cpu_map;
+
+ cpu_map = (struct isst_if_cpu_map *)cmd_ptr;
+ if (cpu_map->logical_cpu >= nr_cpu_ids ||
+ cpu_map->logical_cpu >= num_possible_cpus())
+ return -EINVAL;
+
+ *write_only = 0;
+ cpu_map->physical_cpu = isst_cpu_info[cpu_map->logical_cpu].punit_cpu_id;
+
+ return 0;
+}
+
+static bool match_punit_msr_white_list(int msr)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_msr_white_list); ++i) {
+ if (punit_msr_white_list[i] == msr)
+ return true;
+ }
+
+ return false;
+}
+
+static long isst_if_msr_cmd_req(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_msr_cmd *msr_cmd;
+ int ret;
+
+ msr_cmd = (struct isst_if_msr_cmd *)cmd_ptr;
+
+ if (!match_punit_msr_white_list(msr_cmd->msr))
+ return -EINVAL;
+
+ if (msr_cmd->logical_cpu >= nr_cpu_ids)
+ return -EINVAL;
+
+ if (msr_cmd->read_write) {
+ if (!capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ ret = wrmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ msr_cmd->msr,
+ msr_cmd->data);
+ *write_only = 1;
+ if (!ret && !resume)
+ ret = isst_store_cmd(0, msr_cmd->msr,
+ msr_cmd->logical_cpu,
+ 0, 0, msr_cmd->data);
+ } else {
+ u64 data;
+
+ ret = rdmsrl_safe_on_cpu(msr_cmd->logical_cpu,
+ msr_cmd->msr, &data);
+ if (!ret) {
+ msr_cmd->data = data;
+ *write_only = 0;
+ }
+ }
+
+
+ return ret;
+}
+
+static long isst_if_exec_multi_cmd(void __user *argp, struct isst_if_cmd_cb *cb)
+{
+ unsigned char __user *ptr;
+ u32 cmd_count;
+ u8 *cmd_ptr;
+ long ret;
+ int i;
+
+ /* Each multi command has u32 command count as the first field */
+ if (copy_from_user(&cmd_count, argp, sizeof(cmd_count)))
+ return -EFAULT;
+
+ if (!cmd_count || cmd_count > ISST_IF_CMD_LIMIT)
+ return -EINVAL;
+
+ cmd_ptr = kmalloc(cb->cmd_size, GFP_KERNEL);
+ if (!cmd_ptr)
+ return -ENOMEM;
+
+ /* cb->offset points to start of the command after the command count */
+ ptr = argp + cb->offset;
+
+ for (i = 0; i < cmd_count; ++i) {
+ int wr_only;
+
+ if (signal_pending(current)) {
+ ret = -EINTR;
+ break;
+ }
+
+ if (copy_from_user(cmd_ptr, ptr, cb->cmd_size)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ ret = cb->cmd_callback(cmd_ptr, &wr_only, 0);
+ if (ret)
+ break;
+
+ if (!wr_only && copy_to_user(ptr, cmd_ptr, cb->cmd_size)) {
+ ret = -EFAULT;
+ break;
+ }
+
+ ptr += cb->cmd_size;
+ }
+
+ kfree(cmd_ptr);
+
+ return i ? i : ret;
+}
+
+static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct isst_if_cmd_cb cmd_cb;
+ struct isst_if_cmd_cb *cb;
+ long ret = -ENOTTY;
+ int i;
+
+ switch (cmd) {
+ case ISST_IF_GET_PLATFORM_INFO:
+ ret = isst_if_get_platform_info(argp);
+ break;
+ case ISST_IF_GET_PHY_ID:
+ cmd_cb.cmd_size = sizeof(struct isst_if_cpu_map);
+ cmd_cb.offset = offsetof(struct isst_if_cpu_maps, cpu_map);
+ cmd_cb.cmd_callback = isst_if_proc_phyid_req;
+ ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
+ break;
+ case ISST_IF_IO_CMD:
+ cb = &punit_callbacks[ISST_IF_DEV_MMIO];
+ if (cb->registered)
+ ret = isst_if_exec_multi_cmd(argp, cb);
+ break;
+ case ISST_IF_MBOX_COMMAND:
+ cb = &punit_callbacks[ISST_IF_DEV_MBOX];
+ if (cb->registered)
+ ret = isst_if_exec_multi_cmd(argp, cb);
+ break;
+ case ISST_IF_MSR_COMMAND:
+ cmd_cb.cmd_size = sizeof(struct isst_if_msr_cmd);
+ cmd_cb.offset = offsetof(struct isst_if_msr_cmds, msr_cmd);
+ cmd_cb.cmd_callback = isst_if_msr_cmd_req;
+ ret = isst_if_exec_multi_cmd(argp, &cmd_cb);
+ break;
+ default:
+ for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
+ struct isst_if_cmd_cb *cb = &punit_callbacks[i];
+ int ret;
+
+ if (cb->def_ioctl) {
+ ret = cb->def_ioctl(file, cmd, arg);
+ if (!ret)
+ return ret;
+ }
+ }
+ break;
+ }
+
+ return ret;
+}
+
+/* Lock to prevent module registration when already opened by user space */
+static DEFINE_MUTEX(punit_misc_dev_open_lock);
+/* Lock to allow one shared misc device for all ISST interfaces */
+static DEFINE_MUTEX(punit_misc_dev_reg_lock);
+static int misc_usage_count;
+static int misc_device_ret;
+static int misc_device_open;
+
+static int isst_if_open(struct inode *inode, struct file *file)
+{
+ int i, ret = 0;
+
+ /* Fail open, if a module is going away */
+ mutex_lock(&punit_misc_dev_open_lock);
+ for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
+ struct isst_if_cmd_cb *cb = &punit_callbacks[i];
+
+ if (cb->registered && !try_module_get(cb->owner)) {
+ ret = -ENODEV;
+ break;
+ }
+ }
+ if (ret) {
+ int j;
+
+ for (j = 0; j < i; ++j) {
+ struct isst_if_cmd_cb *cb;
+
+ cb = &punit_callbacks[j];
+ if (cb->registered)
+ module_put(cb->owner);
+ }
+ } else {
+ misc_device_open++;
+ }
+ mutex_unlock(&punit_misc_dev_open_lock);
+
+ return ret;
+}
+
+static int isst_if_relase(struct inode *inode, struct file *f)
+{
+ int i;
+
+ mutex_lock(&punit_misc_dev_open_lock);
+ misc_device_open--;
+ for (i = 0; i < ISST_IF_DEV_MAX; ++i) {
+ struct isst_if_cmd_cb *cb = &punit_callbacks[i];
+
+ if (cb->registered)
+ module_put(cb->owner);
+ }
+ mutex_unlock(&punit_misc_dev_open_lock);
+
+ return 0;
+}
+
+static const struct file_operations isst_if_char_driver_ops = {
+ .open = isst_if_open,
+ .unlocked_ioctl = isst_if_def_ioctl,
+ .release = isst_if_relase,
+};
+
+static struct miscdevice isst_if_char_driver = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "isst_interface",
+ .fops = &isst_if_char_driver_ops,
+};
+
+static const struct x86_cpu_id hpm_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(GRANITERAPIDS_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ATOM_CRESTMONT_X, NULL),
+ {}
+};
+
+static int isst_misc_reg(void)
+{
+ mutex_lock(&punit_misc_dev_reg_lock);
+ if (misc_device_ret)
+ goto unlock_exit;
+
+ if (!misc_usage_count) {
+ const struct x86_cpu_id *id;
+
+ id = x86_match_cpu(hpm_cpu_ids);
+ if (id)
+ isst_hpm_support = true;
+
+ misc_device_ret = isst_if_cpu_info_init();
+ if (misc_device_ret)
+ goto unlock_exit;
+
+ misc_device_ret = misc_register(&isst_if_char_driver);
+ if (misc_device_ret) {
+ isst_if_cpu_info_exit();
+ goto unlock_exit;
+ }
+ }
+ misc_usage_count++;
+
+unlock_exit:
+ mutex_unlock(&punit_misc_dev_reg_lock);
+
+ return misc_device_ret;
+}
+
+static void isst_misc_unreg(void)
+{
+ mutex_lock(&punit_misc_dev_reg_lock);
+ if (misc_usage_count)
+ misc_usage_count--;
+ if (!misc_usage_count && !misc_device_ret) {
+ misc_deregister(&isst_if_char_driver);
+ isst_if_cpu_info_exit();
+ }
+ mutex_unlock(&punit_misc_dev_reg_lock);
+}
+
+/**
+ * isst_if_cdev_register() - Register callback for IOCTL
+ * @device_type: The device type this callback handling.
+ * @cb: Callback structure.
+ *
+ * This function registers a callback to device type. On very first call
+ * it will register a misc device, which is used for user kernel interface.
+ * Other calls simply increment ref count. Registry will fail, if the user
+ * already opened misc device for operation. Also if the misc device
+ * creation failed, then it will not try again and all callers will get
+ * failure code.
+ *
+ * Return: Return the return value from the misc creation device or -EINVAL
+ * for unsupported device type.
+ */
+int isst_if_cdev_register(int device_type, struct isst_if_cmd_cb *cb)
+{
+ int ret;
+
+ if (device_type >= ISST_IF_DEV_MAX)
+ return -EINVAL;
+
+ mutex_lock(&punit_misc_dev_open_lock);
+ /* Device is already open, we don't want to add new callbacks */
+ if (misc_device_open) {
+ mutex_unlock(&punit_misc_dev_open_lock);
+ return -EAGAIN;
+ }
+ if (!cb->api_version)
+ cb->api_version = ISST_IF_API_VERSION;
+ if (cb->api_version > isst_if_api_version)
+ isst_if_api_version = cb->api_version;
+ memcpy(&punit_callbacks[device_type], cb, sizeof(*cb));
+ punit_callbacks[device_type].registered = 1;
+ mutex_unlock(&punit_misc_dev_open_lock);
+
+ ret = isst_misc_reg();
+ if (ret) {
+ /*
+ * No need of mutex as the misc device register failed
+ * as no one can open device yet. Hence no contention.
+ */
+ punit_callbacks[device_type].registered = 0;
+ return ret;
+ }
+ return 0;
+}
+EXPORT_SYMBOL_GPL(isst_if_cdev_register);
+
+/**
+ * isst_if_cdev_unregister() - Unregister callback for IOCTL
+ * @device_type: The device type to unregister.
+ *
+ * This function unregisters the previously registered callback. If this
+ * is the last callback unregistering, then misc device is removed.
+ *
+ * Return: None.
+ */
+void isst_if_cdev_unregister(int device_type)
+{
+ isst_misc_unreg();
+ mutex_lock(&punit_misc_dev_open_lock);
+ punit_callbacks[device_type].def_ioctl = NULL;
+ punit_callbacks[device_type].registered = 0;
+ if (device_type == ISST_IF_DEV_MBOX)
+ isst_delete_hash();
+ mutex_unlock(&punit_misc_dev_open_lock);
+}
+EXPORT_SYMBOL_GPL(isst_if_cdev_unregister);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_common.h b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h
new file mode 100644
index 0000000000..1004f2c9cc
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_common.h
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel Speed Select Interface: Drivers Internal defines
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#ifndef __ISST_IF_COMMON_H
+#define __ISST_IF_COMMON_H
+
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_0 0x3451
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0 0x3459
+
+#define PCI_DEVICE_ID_INTEL_RAPL_PRIO_DEVID_1 0x3251
+#define PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1 0x3259
+
+/*
+ * Validate maximum commands in a single request.
+ * This is enough to handle command to every core in one ioctl, or all
+ * possible message id to one CPU. Limit is also helpful for resonse time
+ * per IOCTL request, as PUNIT may take different times to process each
+ * request and may hold for long for too many commands.
+ */
+#define ISST_IF_CMD_LIMIT 64
+
+#define ISST_IF_API_VERSION 0x01
+#define ISST_IF_DRIVER_VERSION 0x01
+
+#define ISST_IF_DEV_MBOX 0
+#define ISST_IF_DEV_MMIO 1
+#define ISST_IF_DEV_TPMI 2
+#define ISST_IF_DEV_MAX 3
+
+/**
+ * struct isst_if_cmd_cb - Used to register a IOCTL handler
+ * @registered: Used by the common code to store registry. Caller don't
+ * to touch this field
+ * @cmd_size: The command size of the individual command in IOCTL
+ * @offset: Offset to the first valid member in command structure.
+ * This will be the offset of the start of the command
+ * after command count field
+ * @api_version: API version supported for this target. 0, if none.
+ * @owner: Registered module owner
+ * @cmd_callback: Callback function to handle IOCTL. The callback has the
+ * command pointer with data for command. There is a pointer
+ * called write_only, which when set, will not copy the
+ * response to user ioctl buffer. The "resume" argument
+ * can be used to avoid storing the command for replay
+ * during system resume
+ * @def_ioctl: Default IOCTL handler callback, if there is no match in
+ * the existing list of IOCTL handled by the common handler.
+ *
+ * This structure is used to register an handler for IOCTL. To avoid
+ * code duplication common code handles all the IOCTL command read/write
+ * including handling multiple command in single IOCTL. The caller just
+ * need to execute a command via the registered callback.
+ */
+struct isst_if_cmd_cb {
+ int registered;
+ int cmd_size;
+ int offset;
+ int api_version;
+ struct module *owner;
+ long (*cmd_callback)(u8 *ptr, int *write_only, int resume);
+ long (*def_ioctl)(struct file *file, unsigned int cmd, unsigned long arg);
+};
+
+/* Internal interface functions */
+int isst_if_cdev_register(int type, struct isst_if_cmd_cb *cb);
+void isst_if_cdev_unregister(int type);
+struct pci_dev *isst_if_get_pci_dev(int cpu, int bus, int dev, int fn);
+bool isst_if_mbox_cmd_set_req(struct isst_if_mbox_cmd *mbox_cmd);
+bool isst_if_mbox_cmd_invalid(struct isst_if_mbox_cmd *cmd);
+int isst_store_cmd(int cmd, int sub_command, u32 cpu, int mbox_cmd,
+ u32 param, u64 data);
+void isst_resume_common(void);
+#endif
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
new file mode 100644
index 0000000000..1b6eab0710
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_msr.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Mbox via MSR Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/cpuhotplug.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/topology.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#include "isst_if_common.h"
+
+#define MSR_OS_MAILBOX_INTERFACE 0xB0
+#define MSR_OS_MAILBOX_DATA 0xB1
+#define MSR_OS_MAILBOX_BUSY_BIT 31
+
+/*
+ * Based on experiments count is never more than 1, as the MSR overhead
+ * is enough to finish the command. So here this is the worst case number.
+ */
+#define OS_MAILBOX_RETRY_COUNT 3
+
+static int isst_if_send_mbox_cmd(u8 command, u8 sub_command, u32 parameter,
+ u32 command_data, u32 *response_data)
+{
+ u32 retries;
+ u64 data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ retries = OS_MAILBOX_RETRY_COUNT;
+ do {
+ rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ if (ret)
+ return ret;
+
+ /* Write DATA register */
+ wrmsrl(MSR_OS_MAILBOX_DATA, command_data);
+
+ /* Write command register */
+ data = BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT) |
+ (parameter & GENMASK_ULL(13, 0)) << 16 |
+ (sub_command << 8) |
+ command;
+ wrmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+
+ /* Poll for rb bit == 0 */
+ retries = OS_MAILBOX_RETRY_COUNT;
+ do {
+ rdmsrl(MSR_OS_MAILBOX_INTERFACE, data);
+ if (data & BIT_ULL(MSR_OS_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ continue;
+ }
+
+ if (data & 0xff)
+ return -ENXIO;
+
+ if (response_data) {
+ rdmsrl(MSR_OS_MAILBOX_DATA, data);
+ *response_data = data;
+ }
+ ret = 0;
+ break;
+ } while (--retries);
+
+ return ret;
+}
+
+struct msrl_action {
+ int err;
+ struct isst_if_mbox_cmd *mbox_cmd;
+};
+
+/* revisit, smp_call_function_single should be enough for atomic mailbox! */
+static void msrl_update_func(void *info)
+{
+ struct msrl_action *act = info;
+
+ act->err = isst_if_send_mbox_cmd(act->mbox_cmd->command,
+ act->mbox_cmd->sub_command,
+ act->mbox_cmd->parameter,
+ act->mbox_cmd->req_data,
+ &act->mbox_cmd->resp_data);
+}
+
+static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct msrl_action action;
+ int ret;
+
+ action.mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
+
+ if (isst_if_mbox_cmd_invalid(action.mbox_cmd))
+ return -EINVAL;
+
+ if (isst_if_mbox_cmd_set_req(action.mbox_cmd) &&
+ !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ /*
+ * To complete mailbox command, we need to access two MSRs.
+ * So we don't want race to complete a mailbox transcation.
+ * Here smp_call ensures that msrl_update_func() has no race
+ * and also with wait flag, wait for completion.
+ * smp_call_function_single is using get_cpu() and put_cpu().
+ */
+ ret = smp_call_function_single(action.mbox_cmd->logical_cpu,
+ msrl_update_func, &action, 1);
+ if (ret)
+ return ret;
+
+ if (!action.err && !resume && isst_if_mbox_cmd_set_req(action.mbox_cmd))
+ action.err = isst_store_cmd(action.mbox_cmd->command,
+ action.mbox_cmd->sub_command,
+ action.mbox_cmd->logical_cpu, 1,
+ action.mbox_cmd->parameter,
+ action.mbox_cmd->req_data);
+ *write_only = 0;
+
+ return action.err;
+}
+
+
+static int isst_pm_notify(struct notifier_block *nb,
+ unsigned long mode, void *_unused)
+{
+ switch (mode) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ isst_resume_common();
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block isst_pm_nb = {
+ .notifier_call = isst_pm_notify,
+};
+
+static const struct x86_cpu_id isst_if_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, isst_if_cpu_ids);
+
+static int __init isst_if_mbox_init(void)
+{
+ struct isst_if_cmd_cb cb;
+ const struct x86_cpu_id *id;
+ u64 data;
+ int ret;
+
+ id = x86_match_cpu(isst_if_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ /* Check presence of mailbox MSRs */
+ ret = rdmsrl_safe(MSR_OS_MAILBOX_INTERFACE, &data);
+ if (ret)
+ return ret;
+
+ ret = rdmsrl_safe(MSR_OS_MAILBOX_DATA, &data);
+ if (ret)
+ return ret;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
+ cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
+ cb.cmd_callback = isst_if_mbox_proc_cmd;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
+ if (ret)
+ return ret;
+
+ ret = register_pm_notifier(&isst_pm_nb);
+ if (ret)
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+
+ return ret;
+}
+module_init(isst_if_mbox_init)
+
+static void __exit isst_if_mbox_exit(void)
+{
+ unregister_pm_notifier(&isst_pm_nb);
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+}
+module_exit(isst_if_mbox_exit)
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface mailbox driver");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c
new file mode 100644
index 0000000000..df1fc6c719
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mbox_pci.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: Mbox via PCI Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpufeature.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_if_common.h"
+
+#define PUNIT_MAILBOX_DATA 0xA0
+#define PUNIT_MAILBOX_INTERFACE 0xA4
+#define PUNIT_MAILBOX_BUSY_BIT 31
+
+/*
+ * The average time to complete mailbox commands is less than 40us. Most of
+ * the commands complete in few micro seconds. But the same firmware handles
+ * requests from all power management features.
+ * We can create a scenario where we flood the firmware with requests then
+ * the mailbox response can be delayed for 100s of micro seconds. So define
+ * two timeouts. One for average case and one for long.
+ * If the firmware is taking more than average, just call cond_resched().
+ */
+#define OS_MAILBOX_TIMEOUT_AVG_US 40
+#define OS_MAILBOX_TIMEOUT_MAX_US 1000
+
+struct isst_if_device {
+ struct mutex mutex;
+};
+
+static int isst_if_mbox_cmd(struct pci_dev *pdev,
+ struct isst_if_mbox_cmd *mbox_cmd)
+{
+ s64 tm_delta = 0;
+ ktime_t tm;
+ u32 data;
+ int ret;
+
+ /* Poll for rb bit == 0 */
+ tm = ktime_get();
+ do {
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
+ &data);
+ if (ret)
+ return ret;
+
+ if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
+ continue;
+ }
+ ret = 0;
+ break;
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
+
+ if (ret)
+ return ret;
+
+ /* Write DATA register */
+ ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_DATA,
+ mbox_cmd->req_data);
+ if (ret)
+ return ret;
+
+ /* Write command register */
+ data = BIT_ULL(PUNIT_MAILBOX_BUSY_BIT) |
+ (mbox_cmd->parameter & GENMASK_ULL(13, 0)) << 16 |
+ (mbox_cmd->sub_command << 8) |
+ mbox_cmd->command;
+
+ ret = pci_write_config_dword(pdev, PUNIT_MAILBOX_INTERFACE, data);
+ if (ret)
+ return ret;
+
+ /* Poll for rb bit == 0 */
+ tm_delta = 0;
+ tm = ktime_get();
+ do {
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_INTERFACE,
+ &data);
+ if (ret)
+ return ret;
+
+ if (data & BIT_ULL(PUNIT_MAILBOX_BUSY_BIT)) {
+ ret = -EBUSY;
+ tm_delta = ktime_us_delta(ktime_get(), tm);
+ if (tm_delta > OS_MAILBOX_TIMEOUT_AVG_US)
+ cond_resched();
+ continue;
+ }
+
+ if (data & 0xff)
+ return -ENXIO;
+
+ ret = pci_read_config_dword(pdev, PUNIT_MAILBOX_DATA, &data);
+ if (ret)
+ return ret;
+
+ mbox_cmd->resp_data = data;
+ ret = 0;
+ break;
+ } while (tm_delta < OS_MAILBOX_TIMEOUT_MAX_US);
+
+ return ret;
+}
+
+static long isst_if_mbox_proc_cmd(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_mbox_cmd *mbox_cmd;
+ struct isst_if_device *punit_dev;
+ struct pci_dev *pdev;
+ int ret;
+
+ mbox_cmd = (struct isst_if_mbox_cmd *)cmd_ptr;
+
+ if (isst_if_mbox_cmd_invalid(mbox_cmd))
+ return -EINVAL;
+
+ if (isst_if_mbox_cmd_set_req(mbox_cmd) && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ pdev = isst_if_get_pci_dev(mbox_cmd->logical_cpu, 1, 30, 1);
+ if (!pdev)
+ return -EINVAL;
+
+ punit_dev = pci_get_drvdata(pdev);
+ if (!punit_dev)
+ return -EINVAL;
+
+ /*
+ * Basically we are allowing one complete mailbox transaction on
+ * a mapped PCI device at a time.
+ */
+ mutex_lock(&punit_dev->mutex);
+ ret = isst_if_mbox_cmd(pdev, mbox_cmd);
+ if (!ret && !resume && isst_if_mbox_cmd_set_req(mbox_cmd))
+ ret = isst_store_cmd(mbox_cmd->command,
+ mbox_cmd->sub_command,
+ mbox_cmd->logical_cpu, 1,
+ mbox_cmd->parameter,
+ mbox_cmd->req_data);
+ mutex_unlock(&punit_dev->mutex);
+ if (ret)
+ return ret;
+
+ *write_only = 0;
+
+ return 0;
+}
+
+static const struct pci_device_id isst_if_mbox_ids[] = {
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_0)},
+ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_CFG_MBOX_DEVID_1)},
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, isst_if_mbox_ids);
+
+static int isst_if_mbox_probe(struct pci_dev *pdev,
+ const struct pci_device_id *ent)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_cmd_cb cb;
+ int ret;
+
+ punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
+ if (!punit_dev)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ mutex_init(&punit_dev->mutex);
+ pci_set_drvdata(pdev, punit_dev);
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_mbox_cmd);
+ cb.offset = offsetof(struct isst_if_mbox_cmds, mbox_cmd);
+ cb.cmd_callback = isst_if_mbox_proc_cmd;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MBOX, &cb);
+
+ if (ret)
+ mutex_destroy(&punit_dev->mutex);
+
+ return ret;
+}
+
+static void isst_if_mbox_remove(struct pci_dev *pdev)
+{
+ struct isst_if_device *punit_dev;
+
+ punit_dev = pci_get_drvdata(pdev);
+ isst_if_cdev_unregister(ISST_IF_DEV_MBOX);
+ mutex_destroy(&punit_dev->mutex);
+}
+
+static int __maybe_unused isst_if_resume(struct device *device)
+{
+ isst_resume_common();
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, NULL, isst_if_resume);
+
+static struct pci_driver isst_if_pci_driver = {
+ .name = "isst_if_mbox_pci",
+ .id_table = isst_if_mbox_ids,
+ .probe = isst_if_mbox_probe,
+ .remove = isst_if_mbox_remove,
+ .driver.pm = &isst_if_pm_ops,
+};
+
+module_pci_driver(isst_if_pci_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface pci mailbox driver");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
new file mode 100644
index 0000000000..ff49025ec0
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_if_mmio.c
@@ -0,0 +1,200 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Speed Select Interface: MMIO Interface
+ * Copyright (c) 2019, Intel Corporation.
+ * All rights reserved.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched/signal.h>
+#include <linux/uaccess.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_if_common.h"
+
+struct isst_mmio_range {
+ int beg;
+ int end;
+};
+
+static struct isst_mmio_range mmio_range_devid_0[] = {
+ {0x04, 0x14},
+ {0x20, 0xD0},
+};
+
+static struct isst_mmio_range mmio_range_devid_1[] = {
+ {0x04, 0x14},
+ {0x20, 0x11C},
+};
+
+struct isst_if_device {
+ void __iomem *punit_mmio;
+ u32 range_0[5];
+ u32 range_1[64];
+ struct isst_mmio_range *mmio_range;
+ struct mutex mutex;
+};
+
+static long isst_if_mmio_rd_wr(u8 *cmd_ptr, int *write_only, int resume)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_io_reg *io_reg;
+ struct pci_dev *pdev;
+
+ io_reg = (struct isst_if_io_reg *)cmd_ptr;
+
+ if (io_reg->reg % 4)
+ return -EINVAL;
+
+ if (io_reg->read_write && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ pdev = isst_if_get_pci_dev(io_reg->logical_cpu, 0, 0, 1);
+ if (!pdev)
+ return -EINVAL;
+
+ punit_dev = pci_get_drvdata(pdev);
+ if (!punit_dev)
+ return -EINVAL;
+
+ if (io_reg->reg < punit_dev->mmio_range[0].beg ||
+ io_reg->reg > punit_dev->mmio_range[1].end)
+ return -EINVAL;
+
+ /*
+ * Ensure that operation is complete on a PCI device to avoid read
+ * write race by using per PCI device mutex.
+ */
+ mutex_lock(&punit_dev->mutex);
+ if (io_reg->read_write) {
+ writel(io_reg->value, punit_dev->punit_mmio+io_reg->reg);
+ *write_only = 1;
+ } else {
+ io_reg->value = readl(punit_dev->punit_mmio+io_reg->reg);
+ *write_only = 0;
+ }
+ mutex_unlock(&punit_dev->mutex);
+
+ return 0;
+}
+
+static const struct pci_device_id isst_if_ids[] = {
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_0, &mmio_range_devid_0)},
+ { PCI_DEVICE_DATA(INTEL, RAPL_PRIO_DEVID_1, &mmio_range_devid_1)},
+ { 0 },
+};
+MODULE_DEVICE_TABLE(pci, isst_if_ids);
+
+static int isst_if_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
+{
+ struct isst_if_device *punit_dev;
+ struct isst_if_cmd_cb cb;
+ u32 mmio_base, pcu_base;
+ u64 base_addr;
+ int ret;
+
+ punit_dev = devm_kzalloc(&pdev->dev, sizeof(*punit_dev), GFP_KERNEL);
+ if (!punit_dev)
+ return -ENOMEM;
+
+ ret = pcim_enable_device(pdev);
+ if (ret)
+ return ret;
+
+ ret = pci_read_config_dword(pdev, 0xD0, &mmio_base);
+ if (ret)
+ return ret;
+
+ ret = pci_read_config_dword(pdev, 0xFC, &pcu_base);
+ if (ret)
+ return ret;
+
+ pcu_base &= GENMASK(10, 0);
+ base_addr = (u64)mmio_base << 23 | (u64) pcu_base << 12;
+ punit_dev->punit_mmio = devm_ioremap(&pdev->dev, base_addr, 256);
+ if (!punit_dev->punit_mmio)
+ return -ENOMEM;
+
+ mutex_init(&punit_dev->mutex);
+ pci_set_drvdata(pdev, punit_dev);
+ punit_dev->mmio_range = (struct isst_mmio_range *) ent->driver_data;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_io_reg);
+ cb.offset = offsetof(struct isst_if_io_regs, io_reg);
+ cb.cmd_callback = isst_if_mmio_rd_wr;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_MMIO, &cb);
+ if (ret)
+ mutex_destroy(&punit_dev->mutex);
+
+ return ret;
+}
+
+static void isst_if_remove(struct pci_dev *pdev)
+{
+ struct isst_if_device *punit_dev;
+
+ punit_dev = pci_get_drvdata(pdev);
+ isst_if_cdev_unregister(ISST_IF_DEV_MMIO);
+ mutex_destroy(&punit_dev->mutex);
+}
+
+static int __maybe_unused isst_if_suspend(struct device *device)
+{
+ struct isst_if_device *punit_dev = dev_get_drvdata(device);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
+ punit_dev->range_0[i] = readl(punit_dev->punit_mmio +
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+ punit_dev->range_1[i] = readl(punit_dev->punit_mmio + addr);
+ }
+
+ return 0;
+}
+
+static int __maybe_unused isst_if_resume(struct device *device)
+{
+ struct isst_if_device *punit_dev = dev_get_drvdata(device);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_0); ++i)
+ writel(punit_dev->range_0[i], punit_dev->punit_mmio +
+ punit_dev->mmio_range[0].beg + 4 * i);
+ for (i = 0; i < ARRAY_SIZE(punit_dev->range_1); ++i) {
+ u32 addr;
+
+ addr = punit_dev->mmio_range[1].beg + 4 * i;
+ if (addr > punit_dev->mmio_range[1].end)
+ break;
+
+ writel(punit_dev->range_1[i], punit_dev->punit_mmio + addr);
+ }
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(isst_if_pm_ops, isst_if_suspend, isst_if_resume);
+
+static struct pci_driver isst_if_pci_driver = {
+ .name = "isst_if_pci",
+ .id_table = isst_if_ids,
+ .probe = isst_if_probe,
+ .remove = isst_if_remove,
+ .driver.pm = &isst_if_pm_ops,
+};
+
+module_pci_driver(isst_if_pci_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel speed select interface mmio driver");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi.c
new file mode 100644
index 0000000000..1797219153
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * isst_tpmi.c: SST TPMI interface
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ * All Rights Reserved.
+ *
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/module.h>
+#include <linux/intel_tpmi.h>
+
+#include "isst_tpmi_core.h"
+
+static int intel_sst_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+ int ret;
+
+ ret = tpmi_sst_init();
+ if (ret)
+ return ret;
+
+ ret = tpmi_sst_dev_add(auxdev);
+ if (ret)
+ tpmi_sst_exit();
+
+ return ret;
+}
+
+static void intel_sst_remove(struct auxiliary_device *auxdev)
+{
+ tpmi_sst_dev_remove(auxdev);
+ tpmi_sst_exit();
+}
+
+static int intel_sst_suspend(struct device *dev)
+{
+ tpmi_sst_dev_suspend(to_auxiliary_dev(dev));
+
+ return 0;
+}
+
+static int intel_sst_resume(struct device *dev)
+{
+ tpmi_sst_dev_resume(to_auxiliary_dev(dev));
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(intel_sst_pm, intel_sst_suspend, intel_sst_resume);
+
+static const struct auxiliary_device_id intel_sst_id_table[] = {
+ { .name = "intel_vsec.tpmi-sst" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, intel_sst_id_table);
+
+static struct auxiliary_driver intel_sst_aux_driver = {
+ .id_table = intel_sst_id_table,
+ .remove = intel_sst_remove,
+ .probe = intel_sst_probe,
+ .driver = {
+ .pm = pm_sleep_ptr(&intel_sst_pm),
+ },
+};
+
+module_auxiliary_driver(intel_sst_aux_driver);
+
+MODULE_IMPORT_NS(INTEL_TPMI_SST);
+MODULE_DESCRIPTION("Intel TPMI SST Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
new file mode 100644
index 0000000000..63faa2ea83
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.c
@@ -0,0 +1,1442 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * isst_tpmi.c: SST TPMI interface core
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * This information will be useful to understand flows:
+ * In the current generation of platforms, TPMI is supported via OOB
+ * PCI device. This PCI device has one instance per CPU package.
+ * There is a unique TPMI ID for SST. Each TPMI ID also has multiple
+ * entries, representing per power domain information.
+ *
+ * There is one dev file for complete SST information and control same as the
+ * prior generation of hardware. User spaces don't need to know how the
+ * information is presented by the hardware. The TPMI core module implements
+ * the hardware mapping.
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/delay.h>
+#include <linux/intel_tpmi.h>
+#include <linux/fs.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <uapi/linux/isst_if.h>
+
+#include "isst_tpmi_core.h"
+#include "isst_if_common.h"
+
+/* Supported SST hardware version by this driver */
+#define ISST_HEADER_VERSION 1
+
+/*
+ * Used to indicate if value read from MMIO needs to get multiplied
+ * to get to a standard unit or not.
+ */
+#define SST_MUL_FACTOR_NONE 1
+
+/* Define 100 as a scaling factor frequency ratio to frequency conversion */
+#define SST_MUL_FACTOR_FREQ 100
+
+/* All SST regs are 64 bit size */
+#define SST_REG_SIZE 8
+
+/**
+ * struct sst_header - SST main header
+ * @interface_version: Version number for this interface
+ * @cap_mask: Bitmask of the supported sub features. 1=the sub feature is enabled.
+ * 0=disabled.
+ * Bit[8]= SST_CP enable (1), disable (0)
+ * bit[9]= SST_PP enable (1), disable (0)
+ * other bits are reserved for future use
+ * @cp_offset: Qword (8 bytes) offset to the SST_CP register bank
+ * @pp_offset: Qword (8 bytes) offset to the SST_PP register bank
+ * @reserved: Reserved for future use
+ *
+ * This register allows SW to discover SST capability and the offsets to SST-CP
+ * and SST-PP register banks.
+ */
+struct sst_header {
+ u8 interface_version;
+ u8 cap_mask;
+ u8 cp_offset;
+ u8 pp_offset;
+ u32 reserved;
+} __packed;
+
+/**
+ * struct cp_header - SST-CP (core-power) header
+ * @feature_id: 0=SST-CP, 1=SST-PP, 2=SST-BF, 3=SST-TF
+ * @feature_rev: Interface Version number for this SST feature
+ * @ratio_unit: Frequency ratio unit. 00: 100MHz. All others are reserved
+ * @reserved: Reserved for future use
+ *
+ * This structure is used store SST-CP header. This is packed to the same
+ * format as defined in the specifications.
+ */
+struct cp_header {
+ u64 feature_id :4;
+ u64 feature_rev :8;
+ u64 ratio_unit :2;
+ u64 reserved :50;
+} __packed;
+
+/**
+ * struct pp_header - SST-PP (Perf profile) header
+ * @feature_id: 0=SST-CP, 1=SST-PP, 2=SST-BF, 3=SST-TF
+ * @feature_rev: Interface Version number for this SST feature
+ * @level_en_mask: SST-PP level enable/disable fuse mask
+ * @allowed_level_mask: Allowed level mask used for dynamic config level switching
+ * @reserved0: Reserved for future use
+ * @ratio_unit: Frequency ratio unit. 00: 100MHz. All others are reserved
+ * @block_size: Size of PP block in Qword unit (8 bytes)
+ * @dynamic_switch: If set (1), dynamic switching of SST PP is supported
+ * @memory_ratio_unit: Memory Controller frequency ratio unit. 00: 100MHz, others reserved
+ * @reserved1: Reserved for future use
+ *
+ * This structure is used store SST-PP header. This is packed to the same
+ * format as defined in the specifications.
+ */
+struct pp_header {
+ u64 feature_id :4;
+ u64 feature_rev :8;
+ u64 level_en_mask :8;
+ u64 allowed_level_mask :8;
+ u64 reserved0 :4;
+ u64 ratio_unit :2;
+ u64 block_size :8;
+ u64 dynamic_switch :1;
+ u64 memory_ratio_unit :2;
+ u64 reserved1 :19;
+} __packed;
+
+/**
+ * struct feature_offset - Offsets to SST-PP features
+ * @pp_offset: Qword offset within PP level for the SST_PP register bank
+ * @bf_offset: Qword offset within PP level for the SST_BF register bank
+ * @tf_offset: Qword offset within PP level for the SST_TF register bank
+ * @reserved: Reserved for future use
+ *
+ * This structure is used store offsets for SST features in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct feature_offset {
+ u64 pp_offset :8;
+ u64 bf_offset :8;
+ u64 tf_offset :8;
+ u64 reserved :40;
+} __packed;
+
+/**
+ * struct levels_offset - Offsets to each SST PP level
+ * @sst_pp_level0_offset: Qword offset to the register block of PP level 0
+ * @sst_pp_level1_offset: Qword offset to the register block of PP level 1
+ * @sst_pp_level2_offset: Qword offset to the register block of PP level 2
+ * @sst_pp_level3_offset: Qword offset to the register block of PP level 3
+ * @sst_pp_level4_offset: Qword offset to the register block of PP level 4
+ * @reserved: Reserved for future use
+ *
+ * This structure is used store offsets of SST PP levels in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct levels_offset {
+ u64 sst_pp_level0_offset :8;
+ u64 sst_pp_level1_offset :8;
+ u64 sst_pp_level2_offset :8;
+ u64 sst_pp_level3_offset :8;
+ u64 sst_pp_level4_offset :8;
+ u64 reserved :24;
+} __packed;
+
+/**
+ * struct pp_control_offset - Offsets for SST PP controls
+ * @perf_level: A SST-PP level that SW intends to switch to
+ * @perf_level_lock: SST-PP level select lock. 0 - unlocked. 1 - locked till next reset
+ * @resvd0: Reserved for future use
+ * @current_state: Bit mask to control the enable(1)/disable(0) state of each feature
+ * of the current PP level, bit 0 = BF, bit 1 = TF, bit 2-7 = reserved
+ * @reserved: Reserved for future use
+ *
+ * This structure is used store offsets of SST PP controls in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct pp_control_offset {
+ u64 perf_level :3;
+ u64 perf_level_lock :1;
+ u64 resvd0 :4;
+ u64 current_state :8;
+ u64 reserved :48;
+} __packed;
+
+/**
+ * struct pp_status_offset - Offsets for SST PP status fields
+ * @sst_pp_level: Returns the current SST-PP level
+ * @sst_pp_lock: Returns the lock bit setting of perf_level_lock in pp_control_offset
+ * @error_type: Returns last error of SST-PP level change request. 0: no error,
+ * 1: level change not allowed, others: reserved
+ * @feature_state: Bit mask to indicate the enable(1)/disable(0) state of each feature of the
+ * current PP level. bit 0 = BF, bit 1 = TF, bit 2-7 reserved
+ * @reserved0: Reserved for future use
+ * @feature_error_type: Returns last error of the specific feature. Three error_type bits per
+ * feature. i.e. ERROR_TYPE[2:0] for BF, ERROR_TYPE[5:3] for TF, etc.
+ * 0x0: no error, 0x1: The specific feature is not supported by the hardware.
+ * 0x2-0x6: Reserved. 0x7: feature state change is not allowed.
+ * @reserved1: Reserved for future use
+ *
+ * This structure is used store offsets of SST PP status in the register bank.
+ * This is packed to the same format as defined in the specifications.
+ */
+struct pp_status_offset {
+ u64 sst_pp_level :3;
+ u64 sst_pp_lock :1;
+ u64 error_type :4;
+ u64 feature_state :8;
+ u64 reserved0 :16;
+ u64 feature_error_type : 24;
+ u64 reserved1 :8;
+} __packed;
+
+/**
+ * struct perf_level - Used to store perf level and mmio offset
+ * @mmio_offset: mmio offset for a perf level
+ * @level: perf level for this offset
+ *
+ * This structure is used store final mmio offset of each perf level from the
+ * SST base mmio offset.
+ */
+struct perf_level {
+ int mmio_offset;
+ int level;
+};
+
+/**
+ * struct tpmi_per_power_domain_info - Store per power_domain SST info
+ * @package_id: Package id for this power_domain
+ * @power_domain_id: Power domain id, Each entry from the SST-TPMI instance is a power_domain.
+ * @max_level: Max possible PP level possible for this power_domain
+ * @ratio_unit: Ratio unit for converting to MHz
+ * @avx_levels: Number of AVX levels
+ * @pp_block_size: Block size from PP header
+ * @sst_header: Store SST header for this power_domain
+ * @cp_header: Store SST-CP header for this power_domain
+ * @pp_header: Store SST-PP header for this power_domain
+ * @perf_levels: Pointer to each perf level to map level to mmio offset
+ * @feature_offsets: Store feature offsets for each PP-level
+ * @control_offset: Store the control offset for each PP-level
+ * @status_offset: Store the status offset for each PP-level
+ * @sst_base: Mapped SST base IO memory
+ * @auxdev: Auxiliary device instance enumerated this instance
+ * @saved_sst_cp_control: Save SST-CP control configuration to store restore for suspend/resume
+ * @saved_clos_configs: Save SST-CP CLOS configuration to store restore for suspend/resume
+ * @saved_clos_assocs: Save SST-CP CLOS association to store restore for suspend/resume
+ * @saved_pp_control: Save SST-PP control information to store restore for suspend/resume
+ *
+ * This structure is used store complete SST information for a power_domain. This information
+ * is used to read/write request for any SST IOCTL. Each physical CPU package can have multiple
+ * power_domains. Each power domain describes its own SST information and has its own controls.
+ */
+struct tpmi_per_power_domain_info {
+ int package_id;
+ int power_domain_id;
+ int max_level;
+ int ratio_unit;
+ int avx_levels;
+ int pp_block_size;
+ struct sst_header sst_header;
+ struct cp_header cp_header;
+ struct pp_header pp_header;
+ struct perf_level *perf_levels;
+ struct feature_offset feature_offsets;
+ struct pp_control_offset control_offset;
+ struct pp_status_offset status_offset;
+ void __iomem *sst_base;
+ struct auxiliary_device *auxdev;
+ u64 saved_sst_cp_control;
+ u64 saved_clos_configs[4];
+ u64 saved_clos_assocs[4];
+ u64 saved_pp_control;
+};
+
+/**
+ * struct tpmi_sst_struct - Store sst info for a package
+ * @package_id: Package id for this aux device instance
+ * @number_of_power_domains: Number of power_domains pointed by power_domain_info pointer
+ * @power_domain_info: Pointer to power domains information
+ *
+ * This structure is used store full SST information for a package.
+ * Each package has a unique OOB PCI device, which enumerates TPMI.
+ * Each Package will have multiple power_domains.
+ */
+struct tpmi_sst_struct {
+ int package_id;
+ int number_of_power_domains;
+ struct tpmi_per_power_domain_info *power_domain_info;
+};
+
+/**
+ * struct tpmi_sst_common_struct - Store all SST instances
+ * @max_index: Maximum instances currently present
+ * @sst_inst: Pointer to per package instance
+ *
+ * Stores every SST Package instance.
+ */
+struct tpmi_sst_common_struct {
+ int max_index;
+ struct tpmi_sst_struct **sst_inst;
+};
+
+/*
+ * Each IOCTL request is processed under this lock. Also used to protect
+ * registration functions and common data structures.
+ */
+static DEFINE_MUTEX(isst_tpmi_dev_lock);
+
+/* Usage count to track, number of TPMI SST instances registered to this core. */
+static int isst_core_usage_count;
+
+/* Stores complete SST information for every package and power_domain */
+static struct tpmi_sst_common_struct isst_common;
+
+#define SST_MAX_AVX_LEVELS 3
+
+#define SST_PP_OFFSET_0 8
+#define SST_PP_OFFSET_1 16
+#define SST_PP_OFFSET_SIZE 8
+
+static int sst_add_perf_profiles(struct auxiliary_device *auxdev,
+ struct tpmi_per_power_domain_info *pd_info,
+ int levels)
+{
+ u64 perf_level_offsets;
+ int i;
+
+ pd_info->perf_levels = devm_kcalloc(&auxdev->dev, levels,
+ sizeof(struct perf_level),
+ GFP_KERNEL);
+ if (!pd_info->perf_levels)
+ return 0;
+
+ pd_info->ratio_unit = pd_info->pp_header.ratio_unit;
+ pd_info->avx_levels = SST_MAX_AVX_LEVELS;
+ pd_info->pp_block_size = pd_info->pp_header.block_size;
+
+ /* Read PP Offset 0: Get feature offset with PP level */
+ *((u64 *)&pd_info->feature_offsets) = readq(pd_info->sst_base +
+ pd_info->sst_header.pp_offset +
+ SST_PP_OFFSET_0);
+
+ perf_level_offsets = readq(pd_info->sst_base + pd_info->sst_header.pp_offset +
+ SST_PP_OFFSET_1);
+
+ for (i = 0; i < levels; ++i) {
+ u64 offset;
+
+ offset = perf_level_offsets & (0xffULL << (i * SST_PP_OFFSET_SIZE));
+ offset >>= (i * 8);
+ offset &= 0xff;
+ offset *= 8; /* Convert to byte from QWORD offset */
+ pd_info->perf_levels[i].mmio_offset = pd_info->sst_header.pp_offset + offset;
+ }
+
+ return 0;
+}
+
+static int sst_main(struct auxiliary_device *auxdev, struct tpmi_per_power_domain_info *pd_info)
+{
+ int i, mask, levels;
+
+ *((u64 *)&pd_info->sst_header) = readq(pd_info->sst_base);
+ pd_info->sst_header.cp_offset *= 8;
+ pd_info->sst_header.pp_offset *= 8;
+
+ if (pd_info->sst_header.interface_version != ISST_HEADER_VERSION) {
+ dev_err(&auxdev->dev, "SST: Unsupported version:%x\n",
+ pd_info->sst_header.interface_version);
+ return -ENODEV;
+ }
+
+ /* Read SST CP Header */
+ *((u64 *)&pd_info->cp_header) = readq(pd_info->sst_base + pd_info->sst_header.cp_offset);
+
+ /* Read PP header */
+ *((u64 *)&pd_info->pp_header) = readq(pd_info->sst_base + pd_info->sst_header.pp_offset);
+
+ /* Force level_en_mask level 0 */
+ pd_info->pp_header.level_en_mask |= 0x01;
+
+ mask = 0x01;
+ levels = 0;
+ for (i = 0; i < 8; ++i) {
+ if (pd_info->pp_header.level_en_mask & mask)
+ levels = i;
+ mask <<= 1;
+ }
+ pd_info->max_level = levels;
+ sst_add_perf_profiles(auxdev, pd_info, levels + 1);
+
+ return 0;
+}
+
+/*
+ * Map a package and power_domain id to SST information structure unique for a power_domain.
+ * The caller should call under isst_tpmi_dev_lock.
+ */
+static struct tpmi_per_power_domain_info *get_instance(int pkg_id, int power_domain_id)
+{
+ struct tpmi_per_power_domain_info *power_domain_info;
+ struct tpmi_sst_struct *sst_inst;
+
+ if (pkg_id < 0 || pkg_id > isst_common.max_index ||
+ pkg_id >= topology_max_packages())
+ return NULL;
+
+ sst_inst = isst_common.sst_inst[pkg_id];
+ if (!sst_inst)
+ return NULL;
+
+ if (power_domain_id < 0 || power_domain_id >= sst_inst->number_of_power_domains)
+ return NULL;
+
+ power_domain_info = &sst_inst->power_domain_info[power_domain_id];
+
+ if (power_domain_info && !power_domain_info->sst_base)
+ return NULL;
+
+ return power_domain_info;
+}
+
+static bool disable_dynamic_sst_features(void)
+{
+ u64 value;
+
+ rdmsrl(MSR_PM_ENABLE, value);
+ return !(value & 0x1);
+}
+
+#define _read_cp_info(name_str, name, offset, start, width, mult_factor)\
+{\
+ u64 val, mask;\
+ \
+ val = readq(power_domain_info->sst_base + power_domain_info->sst_header.cp_offset +\
+ (offset));\
+ mask = GENMASK_ULL((start + width - 1), start);\
+ val &= mask; \
+ val >>= start;\
+ name = (val * mult_factor);\
+}
+
+#define _write_cp_info(name_str, name, offset, start, width, div_factor)\
+{\
+ u64 val, mask;\
+ \
+ val = readq(power_domain_info->sst_base +\
+ power_domain_info->sst_header.cp_offset + (offset));\
+ mask = GENMASK_ULL((start + width - 1), start);\
+ val &= ~mask;\
+ val |= (name / div_factor) << start;\
+ writeq(val, power_domain_info->sst_base + power_domain_info->sst_header.cp_offset +\
+ (offset));\
+}
+
+#define SST_CP_CONTROL_OFFSET 8
+#define SST_CP_STATUS_OFFSET 16
+
+#define SST_CP_ENABLE_START 0
+#define SST_CP_ENABLE_WIDTH 1
+
+#define SST_CP_PRIORITY_TYPE_START 1
+#define SST_CP_PRIORITY_TYPE_WIDTH 1
+
+static long isst_if_core_power_state(void __user *argp)
+{
+ struct tpmi_per_power_domain_info *power_domain_info;
+ struct isst_core_power core_power;
+
+ if (disable_dynamic_sst_features())
+ return -EFAULT;
+
+ if (copy_from_user(&core_power, argp, sizeof(core_power)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(core_power.socket_id, core_power.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (core_power.get_set) {
+ _write_cp_info("cp_enable", core_power.enable, SST_CP_CONTROL_OFFSET,
+ SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
+ _write_cp_info("cp_prio_type", core_power.priority_type, SST_CP_CONTROL_OFFSET,
+ SST_CP_PRIORITY_TYPE_START, SST_CP_PRIORITY_TYPE_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ } else {
+ /* get */
+ _read_cp_info("cp_enable", core_power.enable, SST_CP_STATUS_OFFSET,
+ SST_CP_ENABLE_START, SST_CP_ENABLE_WIDTH, SST_MUL_FACTOR_NONE)
+ _read_cp_info("cp_prio_type", core_power.priority_type, SST_CP_STATUS_OFFSET,
+ SST_CP_PRIORITY_TYPE_START, SST_CP_PRIORITY_TYPE_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ core_power.supported = !!(power_domain_info->sst_header.cap_mask & BIT(0));
+ if (copy_to_user(argp, &core_power, sizeof(core_power)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+#define SST_CLOS_CONFIG_0_OFFSET 24
+
+#define SST_CLOS_CONFIG_PRIO_START 4
+#define SST_CLOS_CONFIG_PRIO_WIDTH 4
+
+#define SST_CLOS_CONFIG_MIN_START 8
+#define SST_CLOS_CONFIG_MIN_WIDTH 8
+
+#define SST_CLOS_CONFIG_MAX_START 16
+#define SST_CLOS_CONFIG_MAX_WIDTH 8
+
+static long isst_if_clos_param(void __user *argp)
+{
+ struct tpmi_per_power_domain_info *power_domain_info;
+ struct isst_clos_param clos_param;
+
+ if (copy_from_user(&clos_param, argp, sizeof(clos_param)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(clos_param.socket_id, clos_param.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (clos_param.get_set) {
+ _write_cp_info("clos.min_freq", clos_param.min_freq_mhz,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH,
+ SST_MUL_FACTOR_FREQ);
+ _write_cp_info("clos.max_freq", clos_param.max_freq_mhz,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_MAX_START, SST_CLOS_CONFIG_MAX_WIDTH,
+ SST_MUL_FACTOR_FREQ);
+ _write_cp_info("clos.prio", clos_param.prop_prio,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_PRIO_START, SST_CLOS_CONFIG_PRIO_WIDTH,
+ SST_MUL_FACTOR_NONE);
+ } else {
+ /* get */
+ _read_cp_info("clos.min_freq", clos_param.min_freq_mhz,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_MIN_START, SST_CLOS_CONFIG_MIN_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_cp_info("clos.max_freq", clos_param.max_freq_mhz,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_MAX_START, SST_CLOS_CONFIG_MAX_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_cp_info("clos.prio", clos_param.prop_prio,
+ (SST_CLOS_CONFIG_0_OFFSET + clos_param.clos * SST_REG_SIZE),
+ SST_CLOS_CONFIG_PRIO_START, SST_CLOS_CONFIG_PRIO_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ if (copy_to_user(argp, &clos_param, sizeof(clos_param)))
+ return -EFAULT;
+ }
+
+ return 0;
+}
+
+#define SST_CLOS_ASSOC_0_OFFSET 56
+#define SST_CLOS_ASSOC_CPUS_PER_REG 16
+#define SST_CLOS_ASSOC_BITS_PER_CPU 4
+
+static long isst_if_clos_assoc(void __user *argp)
+{
+ struct isst_if_clos_assoc_cmds assoc_cmds;
+ unsigned char __user *ptr;
+ int i;
+
+ /* Each multi command has u16 command count as the first field */
+ if (copy_from_user(&assoc_cmds, argp, sizeof(assoc_cmds)))
+ return -EFAULT;
+
+ if (!assoc_cmds.cmd_count || assoc_cmds.cmd_count > ISST_IF_CMD_LIMIT)
+ return -EINVAL;
+
+ ptr = argp + offsetof(struct isst_if_clos_assoc_cmds, assoc_info);
+ for (i = 0; i < assoc_cmds.cmd_count; ++i) {
+ struct tpmi_per_power_domain_info *power_domain_info;
+ struct isst_if_clos_assoc clos_assoc;
+ int punit_id, punit_cpu_no, pkg_id;
+ struct tpmi_sst_struct *sst_inst;
+ int offset, shift, cpu;
+ u64 val, mask, clos;
+
+ if (copy_from_user(&clos_assoc, ptr, sizeof(clos_assoc)))
+ return -EFAULT;
+
+ if (clos_assoc.socket_id > topology_max_packages())
+ return -EINVAL;
+
+ cpu = clos_assoc.logical_cpu;
+ clos = clos_assoc.clos;
+
+ if (assoc_cmds.punit_cpu_map)
+ punit_cpu_no = cpu;
+ else
+ return -EOPNOTSUPP;
+
+ if (punit_cpu_no < 0)
+ return -EINVAL;
+
+ punit_id = clos_assoc.power_domain_id;
+ pkg_id = clos_assoc.socket_id;
+
+ sst_inst = isst_common.sst_inst[pkg_id];
+
+ if (clos_assoc.power_domain_id > sst_inst->number_of_power_domains)
+ return -EINVAL;
+
+ power_domain_info = &sst_inst->power_domain_info[punit_id];
+
+ offset = SST_CLOS_ASSOC_0_OFFSET +
+ (punit_cpu_no / SST_CLOS_ASSOC_CPUS_PER_REG) * SST_REG_SIZE;
+ shift = punit_cpu_no % SST_CLOS_ASSOC_CPUS_PER_REG;
+ shift *= SST_CLOS_ASSOC_BITS_PER_CPU;
+
+ val = readq(power_domain_info->sst_base +
+ power_domain_info->sst_header.cp_offset + offset);
+ if (assoc_cmds.get_set) {
+ mask = GENMASK_ULL((shift + SST_CLOS_ASSOC_BITS_PER_CPU - 1), shift);
+ val &= ~mask;
+ val |= (clos << shift);
+ writeq(val, power_domain_info->sst_base +
+ power_domain_info->sst_header.cp_offset + offset);
+ } else {
+ val >>= shift;
+ clos_assoc.clos = val & GENMASK(SST_CLOS_ASSOC_BITS_PER_CPU - 1, 0);
+ if (copy_to_user(ptr, &clos_assoc, sizeof(clos_assoc)))
+ return -EFAULT;
+ }
+
+ ptr += sizeof(clos_assoc);
+ }
+
+ return 0;
+}
+
+#define _read_pp_info(name_str, name, offset, start, width, mult_factor)\
+{\
+ u64 val, _mask;\
+ \
+ val = readq(power_domain_info->sst_base + power_domain_info->sst_header.pp_offset +\
+ (offset));\
+ _mask = GENMASK_ULL((start + width - 1), start);\
+ val &= _mask;\
+ val >>= start;\
+ name = (val * mult_factor);\
+}
+
+#define _write_pp_info(name_str, name, offset, start, width, div_factor)\
+{\
+ u64 val, _mask;\
+ \
+ val = readq(power_domain_info->sst_base + power_domain_info->sst_header.pp_offset +\
+ (offset));\
+ _mask = GENMASK((start + width - 1), start);\
+ val &= ~_mask;\
+ val |= (name / div_factor) << start;\
+ writeq(val, power_domain_info->sst_base + power_domain_info->sst_header.pp_offset +\
+ (offset));\
+}
+
+#define _read_bf_level_info(name_str, name, level, offset, start, width, mult_factor)\
+{\
+ u64 val, _mask;\
+ \
+ val = readq(power_domain_info->sst_base +\
+ power_domain_info->perf_levels[level].mmio_offset +\
+ (power_domain_info->feature_offsets.bf_offset * 8) + (offset));\
+ _mask = GENMASK_ULL((start + width - 1), start);\
+ val &= _mask; \
+ val >>= start;\
+ name = (val * mult_factor);\
+}
+
+#define _read_tf_level_info(name_str, name, level, offset, start, width, mult_factor)\
+{\
+ u64 val, _mask;\
+ \
+ val = readq(power_domain_info->sst_base +\
+ power_domain_info->perf_levels[level].mmio_offset +\
+ (power_domain_info->feature_offsets.tf_offset * 8) + (offset));\
+ _mask = GENMASK_ULL((start + width - 1), start);\
+ val &= _mask; \
+ val >>= start;\
+ name = (val * mult_factor);\
+}
+
+#define SST_PP_STATUS_OFFSET 32
+
+#define SST_PP_LEVEL_START 0
+#define SST_PP_LEVEL_WIDTH 3
+
+#define SST_PP_LOCK_START 3
+#define SST_PP_LOCK_WIDTH 1
+
+#define SST_PP_FEATURE_STATE_START 8
+#define SST_PP_FEATURE_STATE_WIDTH 8
+
+#define SST_BF_FEATURE_SUPPORTED_START 12
+#define SST_BF_FEATURE_SUPPORTED_WIDTH 1
+
+#define SST_TF_FEATURE_SUPPORTED_START 12
+#define SST_TF_FEATURE_SUPPORTED_WIDTH 1
+
+static int isst_if_get_perf_level(void __user *argp)
+{
+ struct isst_perf_level_info perf_level;
+ struct tpmi_per_power_domain_info *power_domain_info;
+
+ if (copy_from_user(&perf_level, argp, sizeof(perf_level)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(perf_level.socket_id, perf_level.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ perf_level.max_level = power_domain_info->max_level;
+ perf_level.level_mask = power_domain_info->pp_header.allowed_level_mask;
+ perf_level.feature_rev = power_domain_info->pp_header.feature_rev;
+ _read_pp_info("current_level", perf_level.current_level, SST_PP_STATUS_OFFSET,
+ SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+ _read_pp_info("locked", perf_level.locked, SST_PP_STATUS_OFFSET,
+ SST_PP_LOCK_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+ _read_pp_info("feature_state", perf_level.feature_state, SST_PP_STATUS_OFFSET,
+ SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH, SST_MUL_FACTOR_NONE)
+ perf_level.enabled = !!(power_domain_info->sst_header.cap_mask & BIT(1));
+
+ _read_bf_level_info("bf_support", perf_level.sst_bf_support, 0, 0,
+ SST_BF_FEATURE_SUPPORTED_START, SST_BF_FEATURE_SUPPORTED_WIDTH,
+ SST_MUL_FACTOR_NONE);
+ _read_tf_level_info("tf_support", perf_level.sst_tf_support, 0, 0,
+ SST_TF_FEATURE_SUPPORTED_START, SST_TF_FEATURE_SUPPORTED_WIDTH,
+ SST_MUL_FACTOR_NONE);
+
+ if (copy_to_user(argp, &perf_level, sizeof(perf_level)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define SST_PP_CONTROL_OFFSET 24
+#define SST_PP_LEVEL_CHANGE_TIME_MS 5
+#define SST_PP_LEVEL_CHANGE_RETRY_COUNT 3
+
+static int isst_if_set_perf_level(void __user *argp)
+{
+ struct isst_perf_level_control perf_level;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ int level, retry = 0;
+
+ if (disable_dynamic_sst_features())
+ return -EFAULT;
+
+ if (copy_from_user(&perf_level, argp, sizeof(perf_level)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(perf_level.socket_id, perf_level.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (!(power_domain_info->pp_header.allowed_level_mask & BIT(perf_level.level)))
+ return -EINVAL;
+
+ _read_pp_info("current_level", level, SST_PP_STATUS_OFFSET,
+ SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+
+ /* If the requested new level is same as the current level, reject */
+ if (perf_level.level == level)
+ return -EINVAL;
+
+ _write_pp_info("perf_level", perf_level.level, SST_PP_CONTROL_OFFSET,
+ SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+
+ /* It is possible that firmware is busy (although unlikely), so retry */
+ do {
+ /* Give time to FW to process */
+ msleep(SST_PP_LEVEL_CHANGE_TIME_MS);
+
+ _read_pp_info("current_level", level, SST_PP_STATUS_OFFSET,
+ SST_PP_LEVEL_START, SST_PP_LEVEL_WIDTH, SST_MUL_FACTOR_NONE)
+
+ /* Check if the new level is active */
+ if (perf_level.level == level)
+ break;
+
+ } while (retry++ < SST_PP_LEVEL_CHANGE_RETRY_COUNT);
+
+ /* If the level change didn't happen, return fault */
+ if (perf_level.level != level)
+ return -EFAULT;
+
+ /* Reset the feature state on level change */
+ _write_pp_info("perf_feature", 0, SST_PP_CONTROL_OFFSET,
+ SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ /* Give time to FW to process */
+ msleep(SST_PP_LEVEL_CHANGE_TIME_MS);
+
+ return 0;
+}
+
+static int isst_if_set_perf_feature(void __user *argp)
+{
+ struct isst_perf_feature_control perf_feature;
+ struct tpmi_per_power_domain_info *power_domain_info;
+
+ if (disable_dynamic_sst_features())
+ return -EFAULT;
+
+ if (copy_from_user(&perf_feature, argp, sizeof(perf_feature)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(perf_feature.socket_id, perf_feature.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ _write_pp_info("perf_feature", perf_feature.feature, SST_PP_CONTROL_OFFSET,
+ SST_PP_FEATURE_STATE_START, SST_PP_FEATURE_STATE_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ return 0;
+}
+
+#define _read_pp_level_info(name_str, name, level, offset, start, width, mult_factor)\
+{\
+ u64 val, _mask;\
+ \
+ val = readq(power_domain_info->sst_base +\
+ power_domain_info->perf_levels[level].mmio_offset +\
+ (power_domain_info->feature_offsets.pp_offset * 8) + (offset));\
+ _mask = GENMASK_ULL((start + width - 1), start);\
+ val &= _mask; \
+ val >>= start;\
+ name = (val * mult_factor);\
+}
+
+#define SST_PP_INFO_0_OFFSET 0
+#define SST_PP_INFO_1_OFFSET 8
+#define SST_PP_INFO_2_OFFSET 16
+#define SST_PP_INFO_3_OFFSET 24
+
+/* SST_PP_INFO_4_OFFSET to SST_PP_INFO_9_OFFSET are trl levels */
+#define SST_PP_INFO_4_OFFSET 32
+
+#define SST_PP_INFO_10_OFFSET 80
+#define SST_PP_INFO_11_OFFSET 88
+
+#define SST_PP_P1_SSE_START 0
+#define SST_PP_P1_SSE_WIDTH 8
+
+#define SST_PP_P1_AVX2_START 8
+#define SST_PP_P1_AVX2_WIDTH 8
+
+#define SST_PP_P1_AVX512_START 16
+#define SST_PP_P1_AVX512_WIDTH 8
+
+#define SST_PP_P1_AMX_START 24
+#define SST_PP_P1_AMX_WIDTH 8
+
+#define SST_PP_TDP_START 32
+#define SST_PP_TDP_WIDTH 15
+
+#define SST_PP_T_PROCHOT_START 47
+#define SST_PP_T_PROCHOT_WIDTH 8
+
+#define SST_PP_MAX_MEMORY_FREQ_START 55
+#define SST_PP_MAX_MEMORY_FREQ_WIDTH 7
+
+#define SST_PP_COOLING_TYPE_START 62
+#define SST_PP_COOLING_TYPE_WIDTH 2
+
+#define SST_PP_TRL_0_RATIO_0_START 0
+#define SST_PP_TRL_0_RATIO_0_WIDTH 8
+
+#define SST_PP_TRL_CORES_BUCKET_0_START 0
+#define SST_PP_TRL_CORES_BUCKET_0_WIDTH 8
+
+#define SST_PP_CORE_RATIO_P0_START 0
+#define SST_PP_CORE_RATIO_P0_WIDTH 8
+
+#define SST_PP_CORE_RATIO_P1_START 8
+#define SST_PP_CORE_RATIO_P1_WIDTH 8
+
+#define SST_PP_CORE_RATIO_PN_START 16
+#define SST_PP_CORE_RATIO_PN_WIDTH 8
+
+#define SST_PP_CORE_RATIO_PM_START 24
+#define SST_PP_CORE_RATIO_PM_WIDTH 8
+
+#define SST_PP_CORE_RATIO_P0_FABRIC_START 32
+#define SST_PP_CORE_RATIO_P0_FABRIC_WIDTH 8
+
+#define SST_PP_CORE_RATIO_P1_FABRIC_START 40
+#define SST_PP_CORE_RATIO_P1_FABRIC_WIDTH 8
+
+#define SST_PP_CORE_RATIO_PM_FABRIC_START 48
+#define SST_PP_CORE_RATIO_PM_FABRIC_WIDTH 8
+
+static int isst_if_get_perf_level_info(void __user *argp)
+{
+ struct isst_perf_level_data_info perf_level;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ int i, j;
+
+ if (copy_from_user(&perf_level, argp, sizeof(perf_level)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(perf_level.socket_id, perf_level.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (perf_level.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ if (!(power_domain_info->pp_header.level_en_mask & BIT(perf_level.level)))
+ return -EINVAL;
+
+ _read_pp_level_info("tdp_ratio", perf_level.tdp_ratio, perf_level.level,
+ SST_PP_INFO_0_OFFSET, SST_PP_P1_SSE_START, SST_PP_P1_SSE_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ _read_pp_level_info("base_freq_mhz", perf_level.base_freq_mhz, perf_level.level,
+ SST_PP_INFO_0_OFFSET, SST_PP_P1_SSE_START, SST_PP_P1_SSE_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("base_freq_avx2_mhz", perf_level.base_freq_avx2_mhz, perf_level.level,
+ SST_PP_INFO_0_OFFSET, SST_PP_P1_AVX2_START, SST_PP_P1_AVX2_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("base_freq_avx512_mhz", perf_level.base_freq_avx512_mhz,
+ perf_level.level, SST_PP_INFO_0_OFFSET, SST_PP_P1_AVX512_START,
+ SST_PP_P1_AVX512_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("base_freq_amx_mhz", perf_level.base_freq_amx_mhz, perf_level.level,
+ SST_PP_INFO_0_OFFSET, SST_PP_P1_AMX_START, SST_PP_P1_AMX_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+
+ _read_pp_level_info("thermal_design_power_w", perf_level.thermal_design_power_w,
+ perf_level.level, SST_PP_INFO_1_OFFSET, SST_PP_TDP_START,
+ SST_PP_TDP_WIDTH, SST_MUL_FACTOR_NONE)
+ perf_level.thermal_design_power_w /= 8; /* units are in 1/8th watt */
+ _read_pp_level_info("tjunction_max_c", perf_level.tjunction_max_c, perf_level.level,
+ SST_PP_INFO_1_OFFSET, SST_PP_T_PROCHOT_START, SST_PP_T_PROCHOT_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ _read_pp_level_info("max_memory_freq_mhz", perf_level.max_memory_freq_mhz,
+ perf_level.level, SST_PP_INFO_1_OFFSET, SST_PP_MAX_MEMORY_FREQ_START,
+ SST_PP_MAX_MEMORY_FREQ_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("cooling_type", perf_level.cooling_type, perf_level.level,
+ SST_PP_INFO_1_OFFSET, SST_PP_COOLING_TYPE_START,
+ SST_PP_COOLING_TYPE_WIDTH, SST_MUL_FACTOR_NONE)
+
+ for (i = 0; i < TRL_MAX_LEVELS; ++i) {
+ for (j = 0; j < TRL_MAX_BUCKETS; ++j)
+ _read_pp_level_info("trl*_bucket*_freq_mhz",
+ perf_level.trl_freq_mhz[i][j], perf_level.level,
+ SST_PP_INFO_4_OFFSET + (i * SST_PP_TRL_0_RATIO_0_WIDTH),
+ j * SST_PP_TRL_0_RATIO_0_WIDTH,
+ SST_PP_TRL_0_RATIO_0_WIDTH,
+ SST_MUL_FACTOR_FREQ);
+ }
+
+ for (i = 0; i < TRL_MAX_BUCKETS; ++i)
+ _read_pp_level_info("bucket*_core_count", perf_level.bucket_core_counts[i],
+ perf_level.level, SST_PP_INFO_10_OFFSET,
+ SST_PP_TRL_CORES_BUCKET_0_WIDTH * i,
+ SST_PP_TRL_CORES_BUCKET_0_WIDTH, SST_MUL_FACTOR_NONE)
+
+ perf_level.max_buckets = TRL_MAX_BUCKETS;
+ perf_level.max_trl_levels = TRL_MAX_LEVELS;
+
+ _read_pp_level_info("p0_freq_mhz", perf_level.p0_freq_mhz, perf_level.level,
+ SST_PP_INFO_11_OFFSET, SST_PP_CORE_RATIO_P0_START,
+ SST_PP_CORE_RATIO_P0_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("p1_freq_mhz", perf_level.p1_freq_mhz, perf_level.level,
+ SST_PP_INFO_11_OFFSET, SST_PP_CORE_RATIO_P1_START,
+ SST_PP_CORE_RATIO_P1_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("pn_freq_mhz", perf_level.pn_freq_mhz, perf_level.level,
+ SST_PP_INFO_11_OFFSET, SST_PP_CORE_RATIO_PN_START,
+ SST_PP_CORE_RATIO_PN_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("pm_freq_mhz", perf_level.pm_freq_mhz, perf_level.level,
+ SST_PP_INFO_11_OFFSET, SST_PP_CORE_RATIO_PM_START,
+ SST_PP_CORE_RATIO_PM_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("p0_fabric_freq_mhz", perf_level.p0_fabric_freq_mhz,
+ perf_level.level, SST_PP_INFO_11_OFFSET,
+ SST_PP_CORE_RATIO_P0_FABRIC_START,
+ SST_PP_CORE_RATIO_P0_FABRIC_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("p1_fabric_freq_mhz", perf_level.p1_fabric_freq_mhz,
+ perf_level.level, SST_PP_INFO_11_OFFSET,
+ SST_PP_CORE_RATIO_P1_FABRIC_START,
+ SST_PP_CORE_RATIO_P1_FABRIC_WIDTH, SST_MUL_FACTOR_FREQ)
+ _read_pp_level_info("pm_fabric_freq_mhz", perf_level.pm_fabric_freq_mhz,
+ perf_level.level, SST_PP_INFO_11_OFFSET,
+ SST_PP_CORE_RATIO_PM_FABRIC_START,
+ SST_PP_CORE_RATIO_PM_FABRIC_WIDTH, SST_MUL_FACTOR_FREQ)
+
+ if (copy_to_user(argp, &perf_level, sizeof(perf_level)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define SST_PP_FUSED_CORE_COUNT_START 0
+#define SST_PP_FUSED_CORE_COUNT_WIDTH 8
+
+#define SST_PP_RSLVD_CORE_COUNT_START 8
+#define SST_PP_RSLVD_CORE_COUNT_WIDTH 8
+
+#define SST_PP_RSLVD_CORE_MASK_START 0
+#define SST_PP_RSLVD_CORE_MASK_WIDTH 64
+
+static int isst_if_get_perf_level_mask(void __user *argp)
+{
+ static struct isst_perf_level_cpu_mask cpumask;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ u64 mask;
+
+ if (copy_from_user(&cpumask, argp, sizeof(cpumask)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(cpumask.socket_id, cpumask.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ _read_pp_level_info("mask", mask, cpumask.level, SST_PP_INFO_2_OFFSET,
+ SST_PP_RSLVD_CORE_MASK_START, SST_PP_RSLVD_CORE_MASK_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ cpumask.mask = mask;
+
+ if (!cpumask.punit_cpu_map)
+ return -EOPNOTSUPP;
+
+ if (copy_to_user(argp, &cpumask, sizeof(cpumask)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define SST_BF_INFO_0_OFFSET 0
+#define SST_BF_INFO_1_OFFSET 8
+
+#define SST_BF_P1_HIGH_START 13
+#define SST_BF_P1_HIGH_WIDTH 8
+
+#define SST_BF_P1_LOW_START 21
+#define SST_BF_P1_LOW_WIDTH 8
+
+#define SST_BF_T_PROHOT_START 38
+#define SST_BF_T_PROHOT_WIDTH 8
+
+#define SST_BF_TDP_START 46
+#define SST_BF_TDP_WIDTH 15
+
+static int isst_if_get_base_freq_info(void __user *argp)
+{
+ static struct isst_base_freq_info base_freq;
+ struct tpmi_per_power_domain_info *power_domain_info;
+
+ if (copy_from_user(&base_freq, argp, sizeof(base_freq)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(base_freq.socket_id, base_freq.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (base_freq.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ _read_bf_level_info("p1_high", base_freq.high_base_freq_mhz, base_freq.level,
+ SST_BF_INFO_0_OFFSET, SST_BF_P1_HIGH_START, SST_BF_P1_HIGH_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_bf_level_info("p1_low", base_freq.low_base_freq_mhz, base_freq.level,
+ SST_BF_INFO_0_OFFSET, SST_BF_P1_LOW_START, SST_BF_P1_LOW_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ _read_bf_level_info("BF-TJ", base_freq.tjunction_max_c, base_freq.level,
+ SST_BF_INFO_0_OFFSET, SST_BF_T_PROHOT_START, SST_BF_T_PROHOT_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ _read_bf_level_info("BF-tdp", base_freq.thermal_design_power_w, base_freq.level,
+ SST_BF_INFO_0_OFFSET, SST_BF_TDP_START, SST_BF_TDP_WIDTH,
+ SST_MUL_FACTOR_NONE)
+ base_freq.thermal_design_power_w /= 8; /*unit = 1/8th watt*/
+
+ if (copy_to_user(argp, &base_freq, sizeof(base_freq)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define P1_HI_CORE_MASK_START 0
+#define P1_HI_CORE_MASK_WIDTH 64
+
+static int isst_if_get_base_freq_mask(void __user *argp)
+{
+ static struct isst_perf_level_cpu_mask cpumask;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ u64 mask;
+
+ if (copy_from_user(&cpumask, argp, sizeof(cpumask)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(cpumask.socket_id, cpumask.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ _read_bf_level_info("BF-cpumask", mask, cpumask.level, SST_BF_INFO_1_OFFSET,
+ P1_HI_CORE_MASK_START, P1_HI_CORE_MASK_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ cpumask.mask = mask;
+
+ if (!cpumask.punit_cpu_map)
+ return -EOPNOTSUPP;
+
+ if (copy_to_user(argp, &cpumask, sizeof(cpumask)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static int isst_if_get_tpmi_instance_count(void __user *argp)
+{
+ struct isst_tpmi_instance_count tpmi_inst;
+ struct tpmi_sst_struct *sst_inst;
+ int i;
+
+ if (copy_from_user(&tpmi_inst, argp, sizeof(tpmi_inst)))
+ return -EFAULT;
+
+ if (tpmi_inst.socket_id >= topology_max_packages())
+ return -EINVAL;
+
+ tpmi_inst.count = isst_common.sst_inst[tpmi_inst.socket_id]->number_of_power_domains;
+
+ sst_inst = isst_common.sst_inst[tpmi_inst.socket_id];
+ tpmi_inst.valid_mask = 0;
+ for (i = 0; i < sst_inst->number_of_power_domains; ++i) {
+ struct tpmi_per_power_domain_info *pd_info;
+
+ pd_info = &sst_inst->power_domain_info[i];
+ if (pd_info->sst_base)
+ tpmi_inst.valid_mask |= BIT(i);
+ }
+
+ if (copy_to_user(argp, &tpmi_inst, sizeof(tpmi_inst)))
+ return -EFAULT;
+
+ return 0;
+}
+
+#define SST_TF_INFO_0_OFFSET 0
+#define SST_TF_INFO_1_OFFSET 8
+#define SST_TF_INFO_2_OFFSET 16
+
+#define SST_TF_MAX_LP_CLIP_RATIOS TRL_MAX_LEVELS
+
+#define SST_TF_LP_CLIP_RATIO_0_START 16
+#define SST_TF_LP_CLIP_RATIO_0_WIDTH 8
+
+#define SST_TF_RATIO_0_START 0
+#define SST_TF_RATIO_0_WIDTH 8
+
+#define SST_TF_NUM_CORE_0_START 0
+#define SST_TF_NUM_CORE_0_WIDTH 8
+
+static int isst_if_get_turbo_freq_info(void __user *argp)
+{
+ static struct isst_turbo_freq_info turbo_freq;
+ struct tpmi_per_power_domain_info *power_domain_info;
+ int i, j;
+
+ if (copy_from_user(&turbo_freq, argp, sizeof(turbo_freq)))
+ return -EFAULT;
+
+ power_domain_info = get_instance(turbo_freq.socket_id, turbo_freq.power_domain_id);
+ if (!power_domain_info)
+ return -EINVAL;
+
+ if (turbo_freq.level > power_domain_info->max_level)
+ return -EINVAL;
+
+ turbo_freq.max_buckets = TRL_MAX_BUCKETS;
+ turbo_freq.max_trl_levels = TRL_MAX_LEVELS;
+ turbo_freq.max_clip_freqs = SST_TF_MAX_LP_CLIP_RATIOS;
+
+ for (i = 0; i < turbo_freq.max_clip_freqs; ++i)
+ _read_tf_level_info("lp_clip*", turbo_freq.lp_clip_freq_mhz[i],
+ turbo_freq.level, SST_TF_INFO_0_OFFSET,
+ SST_TF_LP_CLIP_RATIO_0_START +
+ (i * SST_TF_LP_CLIP_RATIO_0_WIDTH),
+ SST_TF_LP_CLIP_RATIO_0_WIDTH, SST_MUL_FACTOR_FREQ)
+
+ for (i = 0; i < TRL_MAX_LEVELS; ++i) {
+ for (j = 0; j < TRL_MAX_BUCKETS; ++j)
+ _read_tf_level_info("cydn*_bucket_*_trl",
+ turbo_freq.trl_freq_mhz[i][j], turbo_freq.level,
+ SST_TF_INFO_2_OFFSET + (i * SST_TF_RATIO_0_WIDTH),
+ j * SST_TF_RATIO_0_WIDTH, SST_TF_RATIO_0_WIDTH,
+ SST_MUL_FACTOR_FREQ)
+ }
+
+ for (i = 0; i < TRL_MAX_BUCKETS; ++i)
+ _read_tf_level_info("bucket_*_core_count", turbo_freq.bucket_core_counts[i],
+ turbo_freq.level, SST_TF_INFO_1_OFFSET,
+ SST_TF_NUM_CORE_0_WIDTH * i, SST_TF_NUM_CORE_0_WIDTH,
+ SST_MUL_FACTOR_NONE)
+
+ if (copy_to_user(argp, &turbo_freq, sizeof(turbo_freq)))
+ return -EFAULT;
+
+ return 0;
+}
+
+static long isst_if_def_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ long ret = -ENOTTY;
+
+ mutex_lock(&isst_tpmi_dev_lock);
+ switch (cmd) {
+ case ISST_IF_COUNT_TPMI_INSTANCES:
+ ret = isst_if_get_tpmi_instance_count(argp);
+ break;
+ case ISST_IF_CORE_POWER_STATE:
+ ret = isst_if_core_power_state(argp);
+ break;
+ case ISST_IF_CLOS_PARAM:
+ ret = isst_if_clos_param(argp);
+ break;
+ case ISST_IF_CLOS_ASSOC:
+ ret = isst_if_clos_assoc(argp);
+ break;
+ case ISST_IF_PERF_LEVELS:
+ ret = isst_if_get_perf_level(argp);
+ break;
+ case ISST_IF_PERF_SET_LEVEL:
+ ret = isst_if_set_perf_level(argp);
+ break;
+ case ISST_IF_PERF_SET_FEATURE:
+ ret = isst_if_set_perf_feature(argp);
+ break;
+ case ISST_IF_GET_PERF_LEVEL_INFO:
+ ret = isst_if_get_perf_level_info(argp);
+ break;
+ case ISST_IF_GET_PERF_LEVEL_CPU_MASK:
+ ret = isst_if_get_perf_level_mask(argp);
+ break;
+ case ISST_IF_GET_BASE_FREQ_INFO:
+ ret = isst_if_get_base_freq_info(argp);
+ break;
+ case ISST_IF_GET_BASE_FREQ_CPU_MASK:
+ ret = isst_if_get_base_freq_mask(argp);
+ break;
+ case ISST_IF_GET_TURBO_FREQ_INFO:
+ ret = isst_if_get_turbo_freq_info(argp);
+ break;
+ default:
+ break;
+ }
+ mutex_unlock(&isst_tpmi_dev_lock);
+
+ return ret;
+}
+
+#define TPMI_SST_AUTO_SUSPEND_DELAY_MS 2000
+
+int tpmi_sst_dev_add(struct auxiliary_device *auxdev)
+{
+ struct intel_tpmi_plat_info *plat_info;
+ struct tpmi_sst_struct *tpmi_sst;
+ int i, ret, pkg = 0, inst = 0;
+ int num_resources;
+
+ plat_info = tpmi_get_platform_data(auxdev);
+ if (!plat_info) {
+ dev_err(&auxdev->dev, "No platform info\n");
+ return -EINVAL;
+ }
+
+ pkg = plat_info->package_id;
+ if (pkg >= topology_max_packages()) {
+ dev_err(&auxdev->dev, "Invalid package id :%x\n", pkg);
+ return -EINVAL;
+ }
+
+ if (isst_common.sst_inst[pkg])
+ return -EEXIST;
+
+ num_resources = tpmi_get_resource_count(auxdev);
+
+ if (!num_resources)
+ return -EINVAL;
+
+ tpmi_sst = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_sst), GFP_KERNEL);
+ if (!tpmi_sst)
+ return -ENOMEM;
+
+ tpmi_sst->power_domain_info = devm_kcalloc(&auxdev->dev, num_resources,
+ sizeof(*tpmi_sst->power_domain_info),
+ GFP_KERNEL);
+ if (!tpmi_sst->power_domain_info)
+ return -ENOMEM;
+
+ tpmi_sst->number_of_power_domains = num_resources;
+
+ for (i = 0; i < num_resources; ++i) {
+ struct resource *res;
+
+ res = tpmi_get_resource_at_index(auxdev, i);
+ if (!res) {
+ tpmi_sst->power_domain_info[i].sst_base = NULL;
+ continue;
+ }
+
+ tpmi_sst->power_domain_info[i].package_id = pkg;
+ tpmi_sst->power_domain_info[i].power_domain_id = i;
+ tpmi_sst->power_domain_info[i].auxdev = auxdev;
+ tpmi_sst->power_domain_info[i].sst_base = devm_ioremap_resource(&auxdev->dev, res);
+ if (IS_ERR(tpmi_sst->power_domain_info[i].sst_base))
+ return PTR_ERR(tpmi_sst->power_domain_info[i].sst_base);
+
+ ret = sst_main(auxdev, &tpmi_sst->power_domain_info[i]);
+ if (ret) {
+ devm_iounmap(&auxdev->dev, tpmi_sst->power_domain_info[i].sst_base);
+ tpmi_sst->power_domain_info[i].sst_base = NULL;
+ continue;
+ }
+
+ ++inst;
+ }
+
+ if (!inst)
+ return -ENODEV;
+
+ tpmi_sst->package_id = pkg;
+ auxiliary_set_drvdata(auxdev, tpmi_sst);
+
+ mutex_lock(&isst_tpmi_dev_lock);
+ if (isst_common.max_index < pkg)
+ isst_common.max_index = pkg;
+ isst_common.sst_inst[pkg] = tpmi_sst;
+ mutex_unlock(&isst_tpmi_dev_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_add, INTEL_TPMI_SST);
+
+void tpmi_sst_dev_remove(struct auxiliary_device *auxdev)
+{
+ struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
+
+ mutex_lock(&isst_tpmi_dev_lock);
+ isst_common.sst_inst[tpmi_sst->package_id] = NULL;
+ mutex_unlock(&isst_tpmi_dev_lock);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_remove, INTEL_TPMI_SST);
+
+void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev)
+{
+ struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
+ struct tpmi_per_power_domain_info *power_domain_info = tpmi_sst->power_domain_info;
+ void __iomem *cp_base;
+
+ cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
+ power_domain_info->saved_sst_cp_control = readq(cp_base + SST_CP_CONTROL_OFFSET);
+
+ memcpy_fromio(power_domain_info->saved_clos_configs, cp_base + SST_CLOS_CONFIG_0_OFFSET,
+ sizeof(power_domain_info->saved_clos_configs));
+
+ memcpy_fromio(power_domain_info->saved_clos_assocs, cp_base + SST_CLOS_ASSOC_0_OFFSET,
+ sizeof(power_domain_info->saved_clos_assocs));
+
+ power_domain_info->saved_pp_control = readq(power_domain_info->sst_base +
+ power_domain_info->sst_header.pp_offset +
+ SST_PP_CONTROL_OFFSET);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_suspend, INTEL_TPMI_SST);
+
+void tpmi_sst_dev_resume(struct auxiliary_device *auxdev)
+{
+ struct tpmi_sst_struct *tpmi_sst = auxiliary_get_drvdata(auxdev);
+ struct tpmi_per_power_domain_info *power_domain_info = tpmi_sst->power_domain_info;
+ void __iomem *cp_base;
+
+ cp_base = power_domain_info->sst_base + power_domain_info->sst_header.cp_offset;
+ writeq(power_domain_info->saved_sst_cp_control, cp_base + SST_CP_CONTROL_OFFSET);
+
+ memcpy_toio(cp_base + SST_CLOS_CONFIG_0_OFFSET, power_domain_info->saved_clos_configs,
+ sizeof(power_domain_info->saved_clos_configs));
+
+ memcpy_toio(cp_base + SST_CLOS_ASSOC_0_OFFSET, power_domain_info->saved_clos_assocs,
+ sizeof(power_domain_info->saved_clos_assocs));
+
+ writeq(power_domain_info->saved_pp_control, power_domain_info->sst_base +
+ power_domain_info->sst_header.pp_offset + SST_PP_CONTROL_OFFSET);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_dev_resume, INTEL_TPMI_SST);
+
+#define ISST_TPMI_API_VERSION 0x02
+
+int tpmi_sst_init(void)
+{
+ struct isst_if_cmd_cb cb;
+ int ret = 0;
+
+ mutex_lock(&isst_tpmi_dev_lock);
+
+ if (isst_core_usage_count) {
+ ++isst_core_usage_count;
+ goto init_done;
+ }
+
+ isst_common.sst_inst = kcalloc(topology_max_packages(),
+ sizeof(*isst_common.sst_inst),
+ GFP_KERNEL);
+ if (!isst_common.sst_inst) {
+ ret = -ENOMEM;
+ goto init_done;
+ }
+
+ memset(&cb, 0, sizeof(cb));
+ cb.cmd_size = sizeof(struct isst_if_io_reg);
+ cb.offset = offsetof(struct isst_if_io_regs, io_reg);
+ cb.cmd_callback = NULL;
+ cb.api_version = ISST_TPMI_API_VERSION;
+ cb.def_ioctl = isst_if_def_ioctl;
+ cb.owner = THIS_MODULE;
+ ret = isst_if_cdev_register(ISST_IF_DEV_TPMI, &cb);
+ if (ret)
+ kfree(isst_common.sst_inst);
+ else
+ ++isst_core_usage_count;
+init_done:
+ mutex_unlock(&isst_tpmi_dev_lock);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_init, INTEL_TPMI_SST);
+
+void tpmi_sst_exit(void)
+{
+ mutex_lock(&isst_tpmi_dev_lock);
+ if (isst_core_usage_count)
+ --isst_core_usage_count;
+
+ if (!isst_core_usage_count) {
+ isst_if_cdev_unregister(ISST_IF_DEV_TPMI);
+ kfree(isst_common.sst_inst);
+ }
+ mutex_unlock(&isst_tpmi_dev_lock);
+}
+EXPORT_SYMBOL_NS_GPL(tpmi_sst_exit, INTEL_TPMI_SST);
+
+MODULE_IMPORT_NS(INTEL_TPMI);
+MODULE_IMPORT_NS(INTEL_TPMI_POWER_DOMAIN);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.h b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.h
new file mode 100644
index 0000000000..900b483703
--- /dev/null
+++ b/drivers/platform/x86/intel/speed_select_if/isst_tpmi_core.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Intel Speed Select Interface: Drivers Internal defines
+ * Copyright (c) 2023, Intel Corporation.
+ * All rights reserved.
+ *
+ */
+
+#ifndef _ISST_TPMI_CORE_H
+#define _ISST_TPMI_CORE_H
+
+int tpmi_sst_init(void);
+void tpmi_sst_exit(void);
+int tpmi_sst_dev_add(struct auxiliary_device *auxdev);
+void tpmi_sst_dev_remove(struct auxiliary_device *auxdev);
+void tpmi_sst_dev_suspend(struct auxiliary_device *auxdev);
+void tpmi_sst_dev_resume(struct auxiliary_device *auxdev);
+#endif