File Index
Files and directories
Sign in
FILE

processor_thermal_wt_hint.c

File preview with syntax highlighting.

linux-6.19.14/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * processor thermal device interface for reading workload type hints
  4 * 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 delay
 10 *	Enable processor thermal device interrupt
 11 *
 12 * - The predicted workload type can be read from MMIO:
 13 *	Offset 0x5B18 shows if there was an interrupt
 14 *	active for change in workload type and also
 15 *	predicted workload type.
 16 *
 17 * Two interface functions are provided to call when there is a
 18 * thermal device interrupt:
 19 * - proc_thermal_check_wt_intr():
 20 *     Check if the interrupt is for change in workload type. Called from
 21 *     interrupt context.
 22 *
 23 * - proc_thermal_wt_intr_callback():
 24 *     Callback for interrupt processing in thread context. This involves
 25 *	sending notification to user space that there is a change in the
 26 *     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	23
 38
 39#define SOC_WT_PREDICTION_INT_ACTIVE	BIT(2)
 40
 41/*
 42 * Closest possible to 1 Second is 1024 ms with programmed time delay
 43 * 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	else
105		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 and
146	 * 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	NULL
186};
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");
~/ Read-only file preview.