257 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * processor thermal device interface for reading workload type hints4 * from the user space. The hints are provided by the firmware.5 *6 * Operation:7 * When user space enables workload type prediction:8 * - Use mailbox to:9 * Configure notification delay10 * Enable processor thermal device interrupt11 *12 * - The predicted workload type can be read from MMIO:13 * Offset 0x5B18 shows if there was an interrupt14 * active for change in workload type and also15 * predicted workload type.16 *17 * Two interface functions are provided to call when there is a18 * thermal device interrupt:19 * - proc_thermal_check_wt_intr():20 * Check if the interrupt is for change in workload type. Called from21 * interrupt context.22 *23 * - proc_thermal_wt_intr_callback():24 * Callback for interrupt processing in thread context. This involves25 * sending notification to user space that there is a change in the26 * workload type.27 *28 * Copyright (c) 2023, Intel Corporation.29 */30 31#include <linux/bitfield.h>32#include <linux/pci.h>33#include "processor_thermal_device.h"34 35#define SOC_WT GENMASK_ULL(47, 40)36 37#define SOC_WT_PREDICTION_INT_ENABLE_BIT 2338 39#define SOC_WT_PREDICTION_INT_ACTIVE BIT(2)40 41/*42 * Closest possible to 1 Second is 1024 ms with programmed time delay43 * of 0x0A.44 */45static u8 notify_delay = 0x0A;46static u16 notify_delay_ms = 1024;47 48static DEFINE_MUTEX(wt_lock);49static u8 wt_enable;50 51/* Show current predicted workload type index */52static ssize_t workload_type_index_show(struct device *dev,53 struct device_attribute *attr,54 char *buf)55{56 struct proc_thermal_device *proc_priv;57 struct pci_dev *pdev = to_pci_dev(dev);58 u64 status = 0;59 int wt;60 61 mutex_lock(&wt_lock);62 if (!wt_enable) {63 mutex_unlock(&wt_lock);64 return -ENODATA;65 }66 67 proc_priv = pci_get_drvdata(pdev);68 69 status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);70 71 mutex_unlock(&wt_lock);72 73 wt = FIELD_GET(SOC_WT, status);74 75 return sysfs_emit(buf, "%d\n", wt);76}77 78static DEVICE_ATTR_RO(workload_type_index);79 80static ssize_t workload_hint_enable_show(struct device *dev,81 struct device_attribute *attr,82 char *buf)83{84 return sysfs_emit(buf, "%d\n", wt_enable);85}86 87static ssize_t workload_hint_enable_store(struct device *dev,88 struct device_attribute *attr,89 const char *buf, size_t size)90{91 struct pci_dev *pdev = to_pci_dev(dev);92 u8 mode;93 int ret;94 95 if (kstrtou8(buf, 10, &mode) || mode > 1)96 return -EINVAL;97 98 mutex_lock(&wt_lock);99 100 if (mode)101 ret = processor_thermal_mbox_interrupt_config(pdev, true,102 SOC_WT_PREDICTION_INT_ENABLE_BIT,103 notify_delay);104 else105 ret = processor_thermal_mbox_interrupt_config(pdev, false,106 SOC_WT_PREDICTION_INT_ENABLE_BIT, 0);107 108 if (ret)109 goto ret_enable_store;110 111 ret = size;112 wt_enable = mode;113 114ret_enable_store:115 mutex_unlock(&wt_lock);116 117 return ret;118}119 120static DEVICE_ATTR_RW(workload_hint_enable);121 122static ssize_t notification_delay_ms_show(struct device *dev,123 struct device_attribute *attr,124 char *buf)125{126 return sysfs_emit(buf, "%u\n", notify_delay_ms);127}128 129static ssize_t notification_delay_ms_store(struct device *dev,130 struct device_attribute *attr,131 const char *buf, size_t size)132{133 struct pci_dev *pdev = to_pci_dev(dev);134 u16 new_tw;135 int ret;136 u8 tm;137 138 /*139 * Time window register value:140 * Formula: (1 + x/4) * power(2,y)141 * x = 2 msbs, that is [30:29] y = 5 [28:24]142 * in INTR_CONFIG register.143 * The result will be in milli seconds.144 * Here, just keep x = 0, and just change y.145 * First round up the user value to power of 2 and146 * then take log2, to get "y" value to program.147 */148 ret = kstrtou16(buf, 10, &new_tw);149 if (ret)150 return ret;151 152 if (!new_tw)153 return -EINVAL;154 155 new_tw = roundup_pow_of_two(new_tw);156 tm = ilog2(new_tw);157 if (tm > 31)158 return -EINVAL;159 160 mutex_lock(&wt_lock);161 162 /* If the workload hint was already enabled, then update with the new delay */163 if (wt_enable)164 ret = processor_thermal_mbox_interrupt_config(pdev, true,165 SOC_WT_PREDICTION_INT_ENABLE_BIT,166 tm);167 168 if (!ret) {169 ret = size;170 notify_delay = tm;171 notify_delay_ms = new_tw;172 }173 174 mutex_unlock(&wt_lock);175 176 return ret;177}178 179static DEVICE_ATTR_RW(notification_delay_ms);180 181static struct attribute *workload_hint_attrs[] = {182 &dev_attr_workload_type_index.attr,183 &dev_attr_workload_hint_enable.attr,184 &dev_attr_notification_delay_ms.attr,185 NULL186};187 188static const struct attribute_group workload_hint_attribute_group = {189 .attrs = workload_hint_attrs,190 .name = "workload_hint"191};192 193/*194 * Callback to check if the interrupt for prediction is active.195 * Caution: Called from the interrupt context.196 */197bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv)198{199 u64 int_status;200 201 int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);202 if (int_status & SOC_WT_PREDICTION_INT_ACTIVE)203 return true;204 205 return false;206}207EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, INT340X_THERMAL);208 209/* Callback to notify user space */210void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)211{212 u64 status;213 214 status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);215 if (!(status & SOC_WT_PREDICTION_INT_ACTIVE))216 return;217 218 sysfs_notify(&pdev->dev.kobj, "workload_hint", "workload_type_index");219}220EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, INT340X_THERMAL);221 222static bool workload_hint_created;223 224int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)225{226 int ret;227 228 ret = sysfs_create_group(&pdev->dev.kobj, &workload_hint_attribute_group);229 if (ret)230 return ret;231 232 workload_hint_created = true;233 234 return 0;235}236EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, INT340X_THERMAL);237 238void proc_thermal_wt_hint_remove(struct pci_dev *pdev)239{240 mutex_lock(&wt_lock);241 if (wt_enable)242 processor_thermal_mbox_interrupt_config(pdev, false,243 SOC_WT_PREDICTION_INT_ENABLE_BIT,244 0);245 mutex_unlock(&wt_lock);246 247 if (workload_hint_created)248 sysfs_remove_group(&pdev->dev.kobj, &workload_hint_attribute_group);249 250 workload_hint_created = false;251}252EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, INT340X_THERMAL);253 254MODULE_IMPORT_NS(INT340X_THERMAL);255MODULE_LICENSE("GPL");256MODULE_DESCRIPTION("Processor Thermal Work Load type hint Interface");257