summaryrefslogtreecommitdiffstats
path: root/drivers/platform/x86/intel/uncore-frequency
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/uncore-frequency
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/uncore-frequency')
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/Kconfig25
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/Makefile11
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c313
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h76
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c413
-rw-r--r--drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c284
6 files changed, 1122 insertions, 0 deletions
diff --git a/drivers/platform/x86/intel/uncore-frequency/Kconfig b/drivers/platform/x86/intel/uncore-frequency/Kconfig
new file mode 100644
index 0000000000..a56d550569
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/Kconfig
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Uncore Frquency control drivers
+#
+
+menu "Intel Uncore Frequency Control"
+ depends on X86_64 || COMPILE_TEST
+
+config INTEL_UNCORE_FREQ_CONTROL_TPMI
+ tristate
+
+config INTEL_UNCORE_FREQ_CONTROL
+ tristate "Intel Uncore frequency control driver"
+ depends on X86_64
+ select INTEL_UNCORE_FREQ_CONTROL_TPMI if INTEL_TPMI
+ help
+ This driver allows control of Uncore frequency limits on
+ supported server platforms.
+
+ Uncore frequency controls RING/LLC (last-level cache) clocks.
+
+ To compile this driver as a module, choose M here: the module
+ will be called intel-uncore-frequency.
+
+endmenu
diff --git a/drivers/platform/x86/intel/uncore-frequency/Makefile b/drivers/platform/x86/intel/uncore-frequency/Makefile
new file mode 100644
index 0000000000..08ff57492b
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/x86/intel/uncore-frequency
+#
+
+obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o
+intel-uncore-frequency-y := uncore-frequency.o
+obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o
+intel-uncore-frequency-common-y := uncore-frequency-common.o
+obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL_TPMI) += intel-uncore-frequency-tpmi.o
+intel-uncore-frequency-tpmi-y := uncore-frequency-tpmi.o
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
new file mode 100644
index 0000000000..33bb58dc3f
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Uncore Frequency Control: Common code implementation
+ * Copyright (c) 2022, Intel Corporation.
+ * All rights reserved.
+ *
+ */
+#include <linux/cpu.h>
+#include <linux/module.h>
+#include "uncore-frequency-common.h"
+
+/* Mutex to control all mutual exclusions */
+static DEFINE_MUTEX(uncore_lock);
+/* Root of the all uncore sysfs kobjs */
+static struct kobject *uncore_root_kobj;
+/* uncore instance count */
+static int uncore_instance_count;
+
+static DEFINE_IDA(intel_uncore_ida);
+
+/* callbacks for actual HW read/write */
+static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max);
+static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max);
+static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq);
+
+static ssize_t show_domain_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct uncore_data *data = container_of(attr, struct uncore_data, domain_id_kobj_attr);
+
+ return sprintf(buf, "%u\n", data->domain_id);
+}
+
+static ssize_t show_fabric_cluster_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct uncore_data *data = container_of(attr, struct uncore_data, fabric_cluster_id_kobj_attr);
+
+ return sprintf(buf, "%u\n", data->cluster_id);
+}
+
+static ssize_t show_package_id(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
+{
+ struct uncore_data *data = container_of(attr, struct uncore_data, package_id_kobj_attr);
+
+ return sprintf(buf, "%u\n", data->package_id);
+}
+
+static ssize_t show_min_max_freq_khz(struct uncore_data *data,
+ char *buf, int min_max)
+{
+ unsigned int min, max;
+ int ret;
+
+ mutex_lock(&uncore_lock);
+ ret = uncore_read(data, &min, &max);
+ mutex_unlock(&uncore_lock);
+ if (ret)
+ return ret;
+
+ if (min_max)
+ return sprintf(buf, "%u\n", max);
+
+ return sprintf(buf, "%u\n", min);
+}
+
+static ssize_t store_min_max_freq_khz(struct uncore_data *data,
+ const char *buf, ssize_t count,
+ int min_max)
+{
+ unsigned int input;
+ int ret;
+
+ if (kstrtouint(buf, 10, &input))
+ return -EINVAL;
+
+ mutex_lock(&uncore_lock);
+ ret = uncore_write(data, input, min_max);
+ mutex_unlock(&uncore_lock);
+
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf)
+{
+ unsigned int freq;
+ int ret;
+
+ mutex_lock(&uncore_lock);
+ ret = uncore_read_freq(data, &freq);
+ mutex_unlock(&uncore_lock);
+ if (ret)
+ return ret;
+
+ return sprintf(buf, "%u\n", freq);
+}
+
+#define store_uncore_min_max(name, min_max) \
+ static ssize_t store_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, \
+ const char *buf, size_t count) \
+ { \
+ struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\
+ \
+ return store_min_max_freq_khz(data, buf, count, \
+ min_max); \
+ }
+
+#define show_uncore_min_max(name, min_max) \
+ static ssize_t show_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf)\
+ { \
+ struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\
+ \
+ return show_min_max_freq_khz(data, buf, min_max); \
+ }
+
+#define show_uncore_perf_status(name) \
+ static ssize_t show_##name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf)\
+ { \
+ struct uncore_data *data = container_of(attr, struct uncore_data, name##_kobj_attr);\
+ \
+ return show_perf_status_freq_khz(data, buf); \
+ }
+
+store_uncore_min_max(min_freq_khz, 0);
+store_uncore_min_max(max_freq_khz, 1);
+
+show_uncore_min_max(min_freq_khz, 0);
+show_uncore_min_max(max_freq_khz, 1);
+
+show_uncore_perf_status(current_freq_khz);
+
+#define show_uncore_data(member_name) \
+ static ssize_t show_##member_name(struct kobject *kobj, \
+ struct kobj_attribute *attr, char *buf)\
+ { \
+ struct uncore_data *data = container_of(attr, struct uncore_data,\
+ member_name##_kobj_attr);\
+ \
+ return sysfs_emit(buf, "%u\n", \
+ data->member_name); \
+ } \
+
+show_uncore_data(initial_min_freq_khz);
+show_uncore_data(initial_max_freq_khz);
+
+#define init_attribute_rw(_name) \
+ do { \
+ sysfs_attr_init(&data->_name##_kobj_attr.attr); \
+ data->_name##_kobj_attr.show = show_##_name; \
+ data->_name##_kobj_attr.store = store_##_name; \
+ data->_name##_kobj_attr.attr.name = #_name; \
+ data->_name##_kobj_attr.attr.mode = 0644; \
+ } while (0)
+
+#define init_attribute_ro(_name) \
+ do { \
+ sysfs_attr_init(&data->_name##_kobj_attr.attr); \
+ data->_name##_kobj_attr.show = show_##_name; \
+ data->_name##_kobj_attr.store = NULL; \
+ data->_name##_kobj_attr.attr.name = #_name; \
+ data->_name##_kobj_attr.attr.mode = 0444; \
+ } while (0)
+
+#define init_attribute_root_ro(_name) \
+ do { \
+ sysfs_attr_init(&data->_name##_kobj_attr.attr); \
+ data->_name##_kobj_attr.show = show_##_name; \
+ data->_name##_kobj_attr.store = NULL; \
+ data->_name##_kobj_attr.attr.name = #_name; \
+ data->_name##_kobj_attr.attr.mode = 0400; \
+ } while (0)
+
+static int create_attr_group(struct uncore_data *data, char *name)
+{
+ int ret, freq, index = 0;
+
+ init_attribute_rw(max_freq_khz);
+ init_attribute_rw(min_freq_khz);
+ init_attribute_ro(initial_min_freq_khz);
+ init_attribute_ro(initial_max_freq_khz);
+ init_attribute_root_ro(current_freq_khz);
+
+ if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) {
+ init_attribute_root_ro(domain_id);
+ data->uncore_attrs[index++] = &data->domain_id_kobj_attr.attr;
+ init_attribute_root_ro(fabric_cluster_id);
+ data->uncore_attrs[index++] = &data->fabric_cluster_id_kobj_attr.attr;
+ init_attribute_root_ro(package_id);
+ data->uncore_attrs[index++] = &data->package_id_kobj_attr.attr;
+ }
+
+ data->uncore_attrs[index++] = &data->max_freq_khz_kobj_attr.attr;
+ data->uncore_attrs[index++] = &data->min_freq_khz_kobj_attr.attr;
+ data->uncore_attrs[index++] = &data->initial_min_freq_khz_kobj_attr.attr;
+ data->uncore_attrs[index++] = &data->initial_max_freq_khz_kobj_attr.attr;
+
+ ret = uncore_read_freq(data, &freq);
+ if (!ret)
+ data->uncore_attrs[index++] = &data->current_freq_khz_kobj_attr.attr;
+
+ data->uncore_attrs[index] = NULL;
+
+ data->uncore_attr_group.name = name;
+ data->uncore_attr_group.attrs = data->uncore_attrs;
+ ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group);
+
+ return ret;
+}
+
+static void delete_attr_group(struct uncore_data *data, char *name)
+{
+ sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group);
+}
+
+int uncore_freq_add_entry(struct uncore_data *data, int cpu)
+{
+ int ret = 0;
+
+ mutex_lock(&uncore_lock);
+ if (data->valid) {
+ /* control cpu changed */
+ data->control_cpu = cpu;
+ goto uncore_unlock;
+ }
+
+ if (data->domain_id != UNCORE_DOMAIN_ID_INVALID) {
+ ret = ida_alloc(&intel_uncore_ida, GFP_KERNEL);
+ if (ret < 0)
+ goto uncore_unlock;
+
+ data->instance_id = ret;
+ sprintf(data->name, "uncore%02d", ret);
+ } else {
+ sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id);
+ }
+
+ uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz);
+
+ ret = create_attr_group(data, data->name);
+ if (ret) {
+ if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
+ ida_free(&intel_uncore_ida, data->instance_id);
+ } else {
+ data->control_cpu = cpu;
+ data->valid = true;
+ }
+
+uncore_unlock:
+ mutex_unlock(&uncore_lock);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY);
+
+void uncore_freq_remove_die_entry(struct uncore_data *data)
+{
+ mutex_lock(&uncore_lock);
+ delete_attr_group(data, data->name);
+ data->control_cpu = -1;
+ data->valid = false;
+ if (data->domain_id != UNCORE_DOMAIN_ID_INVALID)
+ ida_free(&intel_uncore_ida, data->instance_id);
+
+ mutex_unlock(&uncore_lock);
+}
+EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY);
+
+int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
+ int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max),
+ int (*read_freq)(struct uncore_data *data, unsigned int *freq))
+{
+ mutex_lock(&uncore_lock);
+
+ uncore_read = read_control_freq;
+ uncore_write = write_control_freq;
+ uncore_read_freq = read_freq;
+
+ if (!uncore_root_kobj) {
+ struct device *dev_root = bus_get_dev_root(&cpu_subsys);
+
+ if (dev_root) {
+ uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency",
+ &dev_root->kobj);
+ put_device(dev_root);
+ }
+ }
+ if (uncore_root_kobj)
+ ++uncore_instance_count;
+ mutex_unlock(&uncore_lock);
+
+ return uncore_root_kobj ? 0 : -ENOMEM;
+}
+EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY);
+
+void uncore_freq_common_exit(void)
+{
+ mutex_lock(&uncore_lock);
+ --uncore_instance_count;
+ if (!uncore_instance_count) {
+ kobject_put(uncore_root_kobj);
+ uncore_root_kobj = NULL;
+ }
+ mutex_unlock(&uncore_lock);
+}
+EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY);
+
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Uncore Frequency Common Module");
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h
new file mode 100644
index 0000000000..0e5bf507e5
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h
@@ -0,0 +1,76 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Intel Uncore Frequency Control: Common defines and prototypes
+ * Copyright (c) 2022, Intel Corporation.
+ * All rights reserved.
+ *
+ */
+
+#ifndef __INTEL_UNCORE_FREQ_COMMON_H
+#define __INTEL_UNCORE_FREQ_COMMON_H
+
+#include <linux/device.h>
+
+/**
+ * struct uncore_data - Encapsulate all uncore data
+ * @stored_uncore_data: Last user changed MSR 620 value, which will be restored
+ * on system resume.
+ * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init
+ * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init
+ * @control_cpu: Designated CPU for a die to read/write
+ * @valid: Mark the data valid/invalid
+ * @package_id: Package id for this instance
+ * @die_id: Die id for this instance
+ * @domain_id: Power domain id for this instance
+ * @cluster_id: cluster id in a domain
+ * @instance_id: Unique instance id to append to directory name
+ * @name: Sysfs entry name for this instance
+ * @uncore_attr_group: Attribute group storage
+ * @max_freq_khz_kobj_attr: Storage for kobject attribute max_freq_khz
+ * @mix_freq_khz_kobj_attr: Storage for kobject attribute min_freq_khz
+ * @initial_max_freq_khz_kobj_attr: Storage for kobject attribute initial_max_freq_khz
+ * @initial_min_freq_khz_kobj_attr: Storage for kobject attribute initial_min_freq_khz
+ * @current_freq_khz_kobj_attr: Storage for kobject attribute current_freq_khz
+ * @domain_id_kobj_attr: Storage for kobject attribute domain_id
+ * @fabric_cluster_id_kobj_attr: Storage for kobject attribute fabric_cluster_id
+ * @package_id_kobj_attr: Storage for kobject attribute package_id
+ * @uncore_attrs: Attribute storage for group creation
+ *
+ * This structure is used to encapsulate all data related to uncore sysfs
+ * settings for a die/package.
+ */
+struct uncore_data {
+ u64 stored_uncore_data;
+ u32 initial_min_freq_khz;
+ u32 initial_max_freq_khz;
+ int control_cpu;
+ bool valid;
+ int package_id;
+ int die_id;
+ int domain_id;
+ int cluster_id;
+ int instance_id;
+ char name[32];
+
+ struct attribute_group uncore_attr_group;
+ struct kobj_attribute max_freq_khz_kobj_attr;
+ struct kobj_attribute min_freq_khz_kobj_attr;
+ struct kobj_attribute initial_max_freq_khz_kobj_attr;
+ struct kobj_attribute initial_min_freq_khz_kobj_attr;
+ struct kobj_attribute current_freq_khz_kobj_attr;
+ struct kobj_attribute domain_id_kobj_attr;
+ struct kobj_attribute fabric_cluster_id_kobj_attr;
+ struct kobj_attribute package_id_kobj_attr;
+ struct attribute *uncore_attrs[9];
+};
+
+#define UNCORE_DOMAIN_ID_INVALID -1
+
+int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max),
+ int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max),
+ int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq));
+void uncore_freq_common_exit(void);
+int uncore_freq_add_entry(struct uncore_data *data, int cpu);
+void uncore_freq_remove_die_entry(struct uncore_data *data);
+
+#endif
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
new file mode 100644
index 0000000000..7d0a67f8b5
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-tpmi.c
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * uncore-frquency-tpmi: Uncore frequency scaling using TPMI
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ * All Rights Reserved.
+ *
+ * The hardware interface to read/write is basically substitution of
+ * MSR 0x620 and 0x621.
+ * There are specific MMIO offset and bits to get/set minimum and
+ * maximum uncore ratio, similar to MSRs.
+ * The scope of the uncore MSRs was package scope. But TPMI allows
+ * new gen CPUs to have multiple uncore controls at uncore-cluster
+ * level. Each package can have multiple power domains which further
+ * can have multiple clusters.
+ * Here number of power domains = number of resources in this aux
+ * device. There are offsets and bits to discover number of clusters
+ * and offset for each cluster level controls.
+ *
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/intel_tpmi.h>
+
+#include "uncore-frequency-common.h"
+
+#define UNCORE_HEADER_VERSION 1
+#define UNCORE_HEADER_INDEX 0
+#define UNCORE_FABRIC_CLUSTER_OFFSET 8
+
+/* status + control + adv_ctl1 + adv_ctl2 */
+#define UNCORE_FABRIC_CLUSTER_SIZE (4 * 8)
+
+#define UNCORE_STATUS_INDEX 0
+#define UNCORE_CONTROL_INDEX 8
+
+#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
+
+struct tpmi_uncore_struct;
+
+/* Information for each cluster */
+struct tpmi_uncore_cluster_info {
+ bool root_domain;
+ u8 __iomem *cluster_base;
+ struct uncore_data uncore_data;
+ struct tpmi_uncore_struct *uncore_root;
+};
+
+/* Information for each power domain */
+struct tpmi_uncore_power_domain_info {
+ u8 __iomem *uncore_base;
+ int ufs_header_ver;
+ int cluster_count;
+ struct tpmi_uncore_cluster_info *cluster_infos;
+};
+
+/* Information for all power domains in a package */
+struct tpmi_uncore_struct {
+ int power_domain_count;
+ int max_ratio;
+ int min_ratio;
+ struct tpmi_uncore_power_domain_info *pd_info;
+ struct tpmi_uncore_cluster_info root_cluster;
+};
+
+#define UNCORE_GENMASK_MIN_RATIO GENMASK_ULL(21, 15)
+#define UNCORE_GENMASK_MAX_RATIO GENMASK_ULL(14, 8)
+#define UNCORE_GENMASK_CURRENT_RATIO GENMASK_ULL(6, 0)
+
+/* Helper function to read MMIO offset for max/min control frequency */
+static void read_control_freq(struct tpmi_uncore_cluster_info *cluster_info,
+ unsigned int *min, unsigned int *max)
+{
+ u64 control;
+
+ control = readq(cluster_info->cluster_base + UNCORE_CONTROL_INDEX);
+ *max = FIELD_GET(UNCORE_GENMASK_MAX_RATIO, control) * UNCORE_FREQ_KHZ_MULTIPLIER;
+ *min = FIELD_GET(UNCORE_GENMASK_MIN_RATIO, control) * UNCORE_FREQ_KHZ_MULTIPLIER;
+}
+
+#define UNCORE_MAX_RATIO FIELD_MAX(UNCORE_GENMASK_MAX_RATIO)
+
+/* Callback for sysfs read for max/min frequencies. Called under mutex locks */
+static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min,
+ unsigned int *max)
+{
+ struct tpmi_uncore_cluster_info *cluster_info;
+
+ cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
+
+ if (cluster_info->root_domain) {
+ struct tpmi_uncore_struct *uncore_root = cluster_info->uncore_root;
+ int i, _min = 0, _max = 0;
+
+ *min = UNCORE_MAX_RATIO * UNCORE_FREQ_KHZ_MULTIPLIER;
+ *max = 0;
+
+ /*
+ * Get the max/min by looking at each cluster. Get the lowest
+ * min and highest max.
+ */
+ for (i = 0; i < uncore_root->power_domain_count; ++i) {
+ int j;
+
+ for (j = 0; j < uncore_root->pd_info[i].cluster_count; ++j) {
+ read_control_freq(&uncore_root->pd_info[i].cluster_infos[j],
+ &_min, &_max);
+ if (*min > _min)
+ *min = _min;
+ if (*max < _max)
+ *max = _max;
+ }
+ }
+ return 0;
+ }
+
+ read_control_freq(cluster_info, min, max);
+
+ return 0;
+}
+
+/* Helper function to write MMIO offset for max/min control frequency */
+static void write_control_freq(struct tpmi_uncore_cluster_info *cluster_info, unsigned int input,
+ unsigned int min_max)
+{
+ u64 control;
+
+ control = readq(cluster_info->cluster_base + UNCORE_CONTROL_INDEX);
+
+ if (min_max) {
+ control &= ~UNCORE_GENMASK_MAX_RATIO;
+ control |= FIELD_PREP(UNCORE_GENMASK_MAX_RATIO, input);
+ } else {
+ control &= ~UNCORE_GENMASK_MIN_RATIO;
+ control |= FIELD_PREP(UNCORE_GENMASK_MIN_RATIO, input);
+ }
+
+ writeq(control, (cluster_info->cluster_base + UNCORE_CONTROL_INDEX));
+}
+
+/* Callback for sysfs write for max/min frequencies. Called under mutex locks */
+static int uncore_write_control_freq(struct uncore_data *data, unsigned int input,
+ unsigned int min_max)
+{
+ struct tpmi_uncore_cluster_info *cluster_info;
+ struct tpmi_uncore_struct *uncore_root;
+
+ input /= UNCORE_FREQ_KHZ_MULTIPLIER;
+ if (!input || input > UNCORE_MAX_RATIO)
+ return -EINVAL;
+
+ cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
+ uncore_root = cluster_info->uncore_root;
+
+ /* Update each cluster in a package */
+ if (cluster_info->root_domain) {
+ struct tpmi_uncore_struct *uncore_root = cluster_info->uncore_root;
+ int i;
+
+ for (i = 0; i < uncore_root->power_domain_count; ++i) {
+ int j;
+
+ for (j = 0; j < uncore_root->pd_info[i].cluster_count; ++j)
+ write_control_freq(&uncore_root->pd_info[i].cluster_infos[j],
+ input, min_max);
+ }
+
+ if (min_max)
+ uncore_root->max_ratio = input;
+ else
+ uncore_root->min_ratio = input;
+
+ return 0;
+ }
+
+ if (min_max && uncore_root->max_ratio && uncore_root->max_ratio < input)
+ return -EINVAL;
+
+ if (!min_max && uncore_root->min_ratio && uncore_root->min_ratio > input)
+ return -EINVAL;
+
+ write_control_freq(cluster_info, input, min_max);
+
+ return 0;
+}
+
+/* Callback for sysfs read for the current uncore frequency. Called under mutex locks */
+static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
+{
+ struct tpmi_uncore_cluster_info *cluster_info;
+ u64 status;
+
+ cluster_info = container_of(data, struct tpmi_uncore_cluster_info, uncore_data);
+ if (cluster_info->root_domain)
+ return -ENODATA;
+
+ status = readq((u8 __iomem *)cluster_info->cluster_base + UNCORE_STATUS_INDEX);
+ *freq = FIELD_GET(UNCORE_GENMASK_CURRENT_RATIO, status) * UNCORE_FREQ_KHZ_MULTIPLIER;
+
+ return 0;
+}
+
+static void remove_cluster_entries(struct tpmi_uncore_struct *tpmi_uncore)
+{
+ int i;
+
+ for (i = 0; i < tpmi_uncore->power_domain_count; ++i) {
+ struct tpmi_uncore_power_domain_info *pd_info;
+ int j;
+
+ pd_info = &tpmi_uncore->pd_info[i];
+ if (!pd_info->uncore_base)
+ continue;
+
+ for (j = 0; j < pd_info->cluster_count; ++j) {
+ struct tpmi_uncore_cluster_info *cluster_info;
+
+ cluster_info = &pd_info->cluster_infos[j];
+ uncore_freq_remove_die_entry(&cluster_info->uncore_data);
+ }
+ }
+}
+
+#define UNCORE_VERSION_MASK GENMASK_ULL(7, 0)
+#define UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK GENMASK_ULL(15, 8)
+#define UNCORE_CLUSTER_OFF_MASK GENMASK_ULL(7, 0)
+#define UNCORE_MAX_CLUSTER_PER_DOMAIN 8
+
+static int uncore_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id)
+{
+ struct intel_tpmi_plat_info *plat_info;
+ struct tpmi_uncore_struct *tpmi_uncore;
+ int ret, i, pkg = 0;
+ int num_resources;
+
+ /* Get number of power domains, which is equal to number of resources */
+ num_resources = tpmi_get_resource_count(auxdev);
+ if (!num_resources)
+ return -EINVAL;
+
+ /* Register callbacks to uncore core */
+ ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq,
+ uncore_read_freq);
+ if (ret)
+ return ret;
+
+ /* Allocate uncore instance per package */
+ tpmi_uncore = devm_kzalloc(&auxdev->dev, sizeof(*tpmi_uncore), GFP_KERNEL);
+ if (!tpmi_uncore) {
+ ret = -ENOMEM;
+ goto err_rem_common;
+ }
+
+ /* Allocate memory for all power domains in a package */
+ tpmi_uncore->pd_info = devm_kcalloc(&auxdev->dev, num_resources,
+ sizeof(*tpmi_uncore->pd_info),
+ GFP_KERNEL);
+ if (!tpmi_uncore->pd_info) {
+ ret = -ENOMEM;
+ goto err_rem_common;
+ }
+
+ tpmi_uncore->power_domain_count = num_resources;
+
+ /* Get the package ID from the TPMI core */
+ plat_info = tpmi_get_platform_data(auxdev);
+ if (plat_info)
+ pkg = plat_info->package_id;
+ else
+ dev_info(&auxdev->dev, "Platform information is NULL\n");
+
+ for (i = 0; i < num_resources; ++i) {
+ struct tpmi_uncore_power_domain_info *pd_info;
+ struct resource *res;
+ u64 cluster_offset;
+ u8 cluster_mask;
+ int mask, j;
+ u64 header;
+
+ res = tpmi_get_resource_at_index(auxdev, i);
+ if (!res)
+ continue;
+
+ pd_info = &tpmi_uncore->pd_info[i];
+
+ pd_info->uncore_base = devm_ioremap_resource(&auxdev->dev, res);
+ if (IS_ERR(pd_info->uncore_base)) {
+ ret = PTR_ERR(pd_info->uncore_base);
+ /*
+ * Set to NULL so that clean up can still remove other
+ * entries already created if any by
+ * remove_cluster_entries()
+ */
+ pd_info->uncore_base = NULL;
+ goto remove_clusters;
+ }
+
+ /* Check for version and skip this resource if there is mismatch */
+ header = readq(pd_info->uncore_base);
+ pd_info->ufs_header_ver = header & UNCORE_VERSION_MASK;
+ if (pd_info->ufs_header_ver != UNCORE_HEADER_VERSION) {
+ dev_info(&auxdev->dev, "Uncore: Unsupported version:%d\n",
+ pd_info->ufs_header_ver);
+ continue;
+ }
+
+ /* Get Cluster ID Mask */
+ cluster_mask = FIELD_GET(UNCORE_LOCAL_FABRIC_CLUSTER_ID_MASK, header);
+ if (!cluster_mask) {
+ dev_info(&auxdev->dev, "Uncore: Invalid cluster mask:%x\n", cluster_mask);
+ continue;
+ }
+
+ /* Find out number of clusters in this resource */
+ pd_info->cluster_count = hweight8(cluster_mask);
+
+ pd_info->cluster_infos = devm_kcalloc(&auxdev->dev, pd_info->cluster_count,
+ sizeof(struct tpmi_uncore_cluster_info),
+ GFP_KERNEL);
+ if (!pd_info->cluster_infos) {
+ ret = -ENOMEM;
+ goto remove_clusters;
+ }
+ /*
+ * Each byte in the register point to status and control
+ * registers belonging to cluster id 0-8.
+ */
+ cluster_offset = readq(pd_info->uncore_base +
+ UNCORE_FABRIC_CLUSTER_OFFSET);
+
+ for (j = 0; j < pd_info->cluster_count; ++j) {
+ struct tpmi_uncore_cluster_info *cluster_info;
+
+ /* Get the offset for this cluster */
+ mask = (cluster_offset & UNCORE_CLUSTER_OFF_MASK);
+ /* Offset in QWORD, so change to bytes */
+ mask <<= 3;
+
+ cluster_info = &pd_info->cluster_infos[j];
+
+ cluster_info->cluster_base = pd_info->uncore_base + mask;
+
+ cluster_info->uncore_data.package_id = pkg;
+ /* There are no dies like Cascade Lake */
+ cluster_info->uncore_data.die_id = 0;
+ cluster_info->uncore_data.domain_id = i;
+ cluster_info->uncore_data.cluster_id = j;
+
+ cluster_info->uncore_root = tpmi_uncore;
+
+ ret = uncore_freq_add_entry(&cluster_info->uncore_data, 0);
+ if (ret) {
+ cluster_info->cluster_base = NULL;
+ goto remove_clusters;
+ }
+ /* Point to next cluster offset */
+ cluster_offset >>= UNCORE_MAX_CLUSTER_PER_DOMAIN;
+ }
+ }
+
+ auxiliary_set_drvdata(auxdev, tpmi_uncore);
+
+ tpmi_uncore->root_cluster.root_domain = true;
+ tpmi_uncore->root_cluster.uncore_root = tpmi_uncore;
+
+ tpmi_uncore->root_cluster.uncore_data.package_id = pkg;
+ tpmi_uncore->root_cluster.uncore_data.domain_id = UNCORE_DOMAIN_ID_INVALID;
+ ret = uncore_freq_add_entry(&tpmi_uncore->root_cluster.uncore_data, 0);
+ if (ret)
+ goto remove_clusters;
+
+ return 0;
+
+remove_clusters:
+ remove_cluster_entries(tpmi_uncore);
+err_rem_common:
+ uncore_freq_common_exit();
+
+ return ret;
+}
+
+static void uncore_remove(struct auxiliary_device *auxdev)
+{
+ struct tpmi_uncore_struct *tpmi_uncore = auxiliary_get_drvdata(auxdev);
+
+ uncore_freq_remove_die_entry(&tpmi_uncore->root_cluster.uncore_data);
+ remove_cluster_entries(tpmi_uncore);
+
+ uncore_freq_common_exit();
+}
+
+static const struct auxiliary_device_id intel_uncore_id_table[] = {
+ { .name = "intel_vsec.tpmi-uncore" },
+ {}
+};
+MODULE_DEVICE_TABLE(auxiliary, intel_uncore_id_table);
+
+static struct auxiliary_driver intel_uncore_aux_driver = {
+ .id_table = intel_uncore_id_table,
+ .remove = uncore_remove,
+ .probe = uncore_probe,
+};
+
+module_auxiliary_driver(intel_uncore_aux_driver);
+
+MODULE_IMPORT_NS(INTEL_TPMI);
+MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY);
+MODULE_DESCRIPTION("Intel TPMI UFS Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
new file mode 100644
index 0000000000..a3b25253b6
--- /dev/null
+++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Uncore Frequency Setting
+ * Copyright (c) 2022, Intel Corporation.
+ * All rights reserved.
+ *
+ * Provide interface to set MSR 620 at a granularity of per die. On CPU online,
+ * one control CPU is identified per die to read/write limit. This control CPU
+ * is changed, if the CPU state is changed to offline. When the last CPU is
+ * offline in a die then remove the sysfs object for that die.
+ * The majority of actual code is related to sysfs create and read/write
+ * attributes.
+ *
+ * Author: Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>
+ */
+
+#include <linux/cpu.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
+
+#include "uncore-frequency-common.h"
+
+/* Max instances for uncore data, one for each die */
+static int uncore_max_entries __read_mostly;
+/* Storage for uncore data for all instances */
+static struct uncore_data *uncore_instances;
+/* Stores the CPU mask of the target CPUs to use during uncore read/write */
+static cpumask_t uncore_cpu_mask;
+/* CPU online callback register instance */
+static enum cpuhp_state uncore_hp_state __read_mostly;
+
+#define MSR_UNCORE_RATIO_LIMIT 0x620
+#define MSR_UNCORE_PERF_STATUS 0x621
+#define UNCORE_FREQ_KHZ_MULTIPLIER 100000
+
+static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min,
+ unsigned int *max)
+{
+ u64 cap;
+ int ret;
+
+ if (data->control_cpu < 0)
+ return -ENXIO;
+
+ ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ if (ret)
+ return ret;
+
+ *max = (cap & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
+ *min = ((cap & GENMASK(14, 8)) >> 8) * UNCORE_FREQ_KHZ_MULTIPLIER;
+
+ return 0;
+}
+
+static int uncore_write_control_freq(struct uncore_data *data, unsigned int input,
+ unsigned int min_max)
+{
+ int ret;
+ u64 cap;
+
+ input /= UNCORE_FREQ_KHZ_MULTIPLIER;
+ if (!input || input > 0x7F)
+ return -EINVAL;
+
+ if (data->control_cpu < 0)
+ return -ENXIO;
+
+ ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap);
+ if (ret)
+ return ret;
+
+ if (min_max) {
+ cap &= ~0x7F;
+ cap |= input;
+ } else {
+ cap &= ~GENMASK(14, 8);
+ cap |= (input << 8);
+ }
+
+ ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap);
+ if (ret)
+ return ret;
+
+ data->stored_uncore_data = cap;
+
+ return 0;
+}
+
+static int uncore_read_freq(struct uncore_data *data, unsigned int *freq)
+{
+ u64 ratio;
+ int ret;
+
+ if (data->control_cpu < 0)
+ return -ENXIO;
+
+ ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio);
+ if (ret)
+ return ret;
+
+ *freq = (ratio & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER;
+
+ return 0;
+}
+
+/* Caller provides protection */
+static struct uncore_data *uncore_get_instance(unsigned int cpu)
+{
+ int id = topology_logical_die_id(cpu);
+
+ if (id >= 0 && id < uncore_max_entries)
+ return &uncore_instances[id];
+
+ return NULL;
+}
+
+static int uncore_event_cpu_online(unsigned int cpu)
+{
+ struct uncore_data *data;
+ int target;
+
+ /* Check if there is an online cpu in the package for uncore MSR */
+ target = cpumask_any_and(&uncore_cpu_mask, topology_die_cpumask(cpu));
+ if (target < nr_cpu_ids)
+ return 0;
+
+ /* Use this CPU on this die as a control CPU */
+ cpumask_set_cpu(cpu, &uncore_cpu_mask);
+
+ data = uncore_get_instance(cpu);
+ if (!data)
+ return 0;
+
+ data->package_id = topology_physical_package_id(cpu);
+ data->die_id = topology_die_id(cpu);
+ data->domain_id = UNCORE_DOMAIN_ID_INVALID;
+
+ return uncore_freq_add_entry(data, cpu);
+}
+
+static int uncore_event_cpu_offline(unsigned int cpu)
+{
+ struct uncore_data *data;
+ int target;
+
+ data = uncore_get_instance(cpu);
+ if (!data)
+ return 0;
+
+ /* Check if existing cpu is used for uncore MSRs */
+ if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask))
+ return 0;
+
+ /* Find a new cpu to set uncore MSR */
+ target = cpumask_any_but(topology_die_cpumask(cpu), cpu);
+
+ if (target < nr_cpu_ids) {
+ cpumask_set_cpu(target, &uncore_cpu_mask);
+ uncore_freq_add_entry(data, target);
+ } else {
+ uncore_freq_remove_die_entry(data);
+ }
+
+ return 0;
+}
+
+static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode,
+ void *_unused)
+{
+ int i;
+
+ switch (mode) {
+ case PM_POST_HIBERNATION:
+ case PM_POST_RESTORE:
+ case PM_POST_SUSPEND:
+ for (i = 0; i < uncore_max_entries; ++i) {
+ struct uncore_data *data = &uncore_instances[i];
+
+ if (!data || !data->valid || !data->stored_uncore_data)
+ return 0;
+
+ wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT,
+ data->stored_uncore_data);
+ }
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static struct notifier_block uncore_pm_nb = {
+ .notifier_call = uncore_pm_notify,
+};
+
+static const struct x86_cpu_id intel_uncore_cpu_ids[] = {
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_G, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(BROADWELL_D, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SKYLAKE_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ICELAKE_D, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(EMERALDRAPIDS_X, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(ALDERLAKE_L, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_P, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(RAPTORLAKE_S, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE, NULL),
+ X86_MATCH_INTEL_FAM6_MODEL(METEORLAKE_L, NULL),
+ {}
+};
+MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids);
+
+static int __init intel_uncore_init(void)
+{
+ const struct x86_cpu_id *id;
+ int ret;
+
+ if (cpu_feature_enabled(X86_FEATURE_HYPERVISOR))
+ return -ENODEV;
+
+ id = x86_match_cpu(intel_uncore_cpu_ids);
+ if (!id)
+ return -ENODEV;
+
+ uncore_max_entries = topology_max_packages() *
+ topology_max_die_per_package();
+ uncore_instances = kcalloc(uncore_max_entries,
+ sizeof(*uncore_instances), GFP_KERNEL);
+ if (!uncore_instances)
+ return -ENOMEM;
+
+ ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq,
+ uncore_read_freq);
+ if (ret)
+ goto err_free;
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN,
+ "platform/x86/uncore-freq:online",
+ uncore_event_cpu_online,
+ uncore_event_cpu_offline);
+ if (ret < 0)
+ goto err_rem_kobj;
+
+ uncore_hp_state = ret;
+
+ ret = register_pm_notifier(&uncore_pm_nb);
+ if (ret)
+ goto err_rem_state;
+
+ return 0;
+
+err_rem_state:
+ cpuhp_remove_state(uncore_hp_state);
+err_rem_kobj:
+ uncore_freq_common_exit();
+err_free:
+ kfree(uncore_instances);
+
+ return ret;
+}
+module_init(intel_uncore_init)
+
+static void __exit intel_uncore_exit(void)
+{
+ int i;
+
+ unregister_pm_notifier(&uncore_pm_nb);
+ cpuhp_remove_state(uncore_hp_state);
+ for (i = 0; i < uncore_max_entries; ++i)
+ uncore_freq_remove_die_entry(&uncore_instances[i]);
+ uncore_freq_common_exit();
+ kfree(uncore_instances);
+}
+module_exit(intel_uncore_exit)
+
+MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY);
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver");