summaryrefslogtreecommitdiff
path: root/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c')
-rw-r--r--drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c255
1 files changed, 255 insertions, 0 deletions
diff --git a/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
new file mode 100644
index 000000000000..9d5e4c169d1b
--- /dev/null
+++ b/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * processor thermal device interface for reading workload type hints
+ * from the user space. The hints are provided by the firmware.
+ *
+ * Operation:
+ * When user space enables workload type prediction:
+ * - Use mailbox to:
+ * Configure notification delay
+ * Enable processor thermal device interrupt
+ *
+ * - The predicted workload type can be read from MMIO:
+ * Offset 0x5B18 shows if there was an interrupt
+ * active for change in workload type and also
+ * predicted workload type.
+ *
+ * Two interface functions are provided to call when there is a
+ * thermal device interrupt:
+ * - proc_thermal_check_wt_intr():
+ * Check if the interrupt is for change in workload type. Called from
+ * interrupt context.
+ *
+ * - proc_thermal_wt_intr_callback():
+ * Callback for interrupt processing in thread context. This involves
+ * sending notification to user space that there is a change in the
+ * workload type.
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/pci.h>
+#include "processor_thermal_device.h"
+
+#define SOC_WT GENMASK_ULL(47, 40)
+
+#define SOC_WT_PREDICTION_INT_ENABLE_BIT 23
+
+#define SOC_WT_PREDICTION_INT_ACTIVE BIT(2)
+
+/*
+ * Closest possible to 1 Second is 1024 ms with programmed time delay
+ * of 0x0A.
+ */
+static u8 notify_delay = 0x0A;
+static u16 notify_delay_ms = 1024;
+
+static DEFINE_MUTEX(wt_lock);
+static u8 wt_enable;
+
+/* Show current predicted workload type index */
+static ssize_t workload_type_index_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct proc_thermal_device *proc_priv;
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u64 status = 0;
+ int wt;
+
+ mutex_lock(&wt_lock);
+ if (!wt_enable) {
+ mutex_unlock(&wt_lock);
+ return -ENODATA;
+ }
+
+ proc_priv = pci_get_drvdata(pdev);
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+
+ mutex_unlock(&wt_lock);
+
+ wt = FIELD_GET(SOC_WT, status);
+
+ return sysfs_emit(buf, "%d\n", wt);
+}
+
+static DEVICE_ATTR_RO(workload_type_index);
+
+static ssize_t workload_hint_enable_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%d\n", wt_enable);
+}
+
+static ssize_t workload_hint_enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u8 mode;
+ int ret;
+
+ if (kstrtou8(buf, 10, &mode) || mode > 1)
+ return -EINVAL;
+
+ mutex_lock(&wt_lock);
+
+ if (mode)
+ ret = processor_thermal_mbox_interrupt_config(pdev, true,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ notify_delay);
+ else
+ ret = processor_thermal_mbox_interrupt_config(pdev, false,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT, 0);
+
+ if (ret)
+ goto ret_enable_store;
+
+ ret = size;
+ wt_enable = mode;
+
+ret_enable_store:
+ mutex_unlock(&wt_lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(workload_hint_enable);
+
+static ssize_t notification_delay_ms_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ return sysfs_emit(buf, "%u\n", notify_delay_ms);
+}
+
+static ssize_t notification_delay_ms_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct pci_dev *pdev = to_pci_dev(dev);
+ u16 new_tw;
+ int ret;
+ u8 tm;
+
+ /*
+ * Time window register value:
+ * Formula: (1 + x/4) * power(2,y)
+ * x = 2 msbs, that is [30:29] y = 5 [28:24]
+ * in INTR_CONFIG register.
+ * The result will be in milli seconds.
+ * Here, just keep x = 0, and just change y.
+ * First round up the user value to power of 2 and
+ * then take log2, to get "y" value to program.
+ */
+ ret = kstrtou16(buf, 10, &new_tw);
+ if (ret)
+ return ret;
+
+ if (!new_tw)
+ return -EINVAL;
+
+ new_tw = roundup_pow_of_two(new_tw);
+ tm = ilog2(new_tw);
+ if (tm > 31)
+ return -EINVAL;
+
+ mutex_lock(&wt_lock);
+
+ /* If the workload hint was already enabled, then update with the new delay */
+ if (wt_enable)
+ ret = processor_thermal_mbox_interrupt_config(pdev, true,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ tm);
+
+ if (!ret) {
+ ret = size;
+ notify_delay = tm;
+ notify_delay_ms = new_tw;
+ }
+
+ mutex_unlock(&wt_lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR_RW(notification_delay_ms);
+
+static struct attribute *workload_hint_attrs[] = {
+ &dev_attr_workload_type_index.attr,
+ &dev_attr_workload_hint_enable.attr,
+ &dev_attr_notification_delay_ms.attr,
+ NULL
+};
+
+static const struct attribute_group workload_hint_attribute_group = {
+ .attrs = workload_hint_attrs,
+ .name = "workload_hint"
+};
+
+/*
+ * Callback to check if the interrupt for prediction is active.
+ * Caution: Called from the interrupt context.
+ */
+bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv)
+{
+ u64 int_status;
+
+ int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ if (int_status & SOC_WT_PREDICTION_INT_ACTIVE)
+ return true;
+
+ return false;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, INT340X_THERMAL);
+
+/* Callback to notify user space */
+void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ u64 status;
+
+ status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
+ if (!(status & SOC_WT_PREDICTION_INT_ACTIVE))
+ return;
+
+ sysfs_notify(&pdev->dev.kobj, "workload_hint", "workload_type_index");
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, INT340X_THERMAL);
+
+static bool workload_hint_created;
+
+int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
+{
+ int ret;
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &workload_hint_attribute_group);
+ if (ret)
+ return ret;
+
+ workload_hint_created = true;
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, INT340X_THERMAL);
+
+void proc_thermal_wt_hint_remove(struct pci_dev *pdev)
+{
+ mutex_lock(&wt_lock);
+ if (wt_enable)
+ processor_thermal_mbox_interrupt_config(pdev, false,
+ SOC_WT_PREDICTION_INT_ENABLE_BIT,
+ 0);
+ mutex_unlock(&wt_lock);
+
+ if (workload_hint_created)
+ sysfs_remove_group(&pdev->dev.kobj, &workload_hint_attribute_group);
+
+ workload_hint_created = false;
+}
+EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, INT340X_THERMAL);
+
+MODULE_IMPORT_NS(INT340X_THERMAL);
+MODULE_LICENSE("GPL");