xref: /linux/drivers/thermal/intel/int340x_thermal/processor_thermal_wt_hint.c (revision 0506158ac7363a70f0deb49f71d26ccb57e55990)
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_SLOW_PREDICTION_INT_ENABLE_BIT	22
38 #define SOC_WT_PREDICTION_INT_ENABLE_BIT	23
39 
40 #define SOC_WT_PREDICTION_INT_ACTIVE	BIT(2)
41 
42 /*
43  * Closest possible to 1 Second is 1024 ms with programmed time delay
44  * of 0x0A.
45  */
46 static u8 notify_delay = 0x0A;
47 static u16 notify_delay_ms = 1024;
48 
49 static DEFINE_MUTEX(wt_lock);
50 static u8 wt_enable;
51 static u8 wt_slow_enable;
52 
53 /* Show current predicted workload type index */
workload_type_index_show(struct device * dev,struct device_attribute * attr,char * buf)54 static ssize_t workload_type_index_show(struct device *dev,
55 					struct device_attribute *attr,
56 					char *buf)
57 {
58 	struct proc_thermal_device *proc_priv;
59 	struct pci_dev *pdev = to_pci_dev(dev);
60 	u64 status = 0;
61 	int wt;
62 
63 	mutex_lock(&wt_lock);
64 	if (!wt_enable && !wt_slow_enable) {
65 		mutex_unlock(&wt_lock);
66 		return -ENODATA;
67 	}
68 
69 	proc_priv = pci_get_drvdata(pdev);
70 
71 	status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
72 
73 	mutex_unlock(&wt_lock);
74 
75 	wt = FIELD_GET(SOC_WT, status);
76 
77 	return sysfs_emit(buf, "%d\n", wt);
78 }
79 
80 static DEVICE_ATTR_RO(workload_type_index);
81 
workload_hint_enable_show(struct device * dev,struct device_attribute * attr,char * buf)82 static ssize_t workload_hint_enable_show(struct device *dev,
83 					 struct device_attribute *attr,
84 					 char *buf)
85 {
86 	return sysfs_emit(buf, "%d\n", wt_enable);
87 }
88 
workload_hint_enable(struct device * dev,u8 enable_bit,u8 * status,struct device_attribute * attr,const char * buf,size_t size)89 static ssize_t workload_hint_enable(struct device *dev, u8 enable_bit, u8 *status,
90 				    struct device_attribute *attr,
91 				    const char *buf, size_t size)
92 {
93 	struct pci_dev *pdev = to_pci_dev(dev);
94 	u8 mode;
95 	int ret;
96 
97 	if (kstrtou8(buf, 10, &mode) || mode > 1)
98 		return -EINVAL;
99 
100 	mutex_lock(&wt_lock);
101 
102 	if (mode)
103 		ret = processor_thermal_mbox_interrupt_config(pdev, true,
104 							      enable_bit,
105 							      notify_delay);
106 	else
107 		ret = processor_thermal_mbox_interrupt_config(pdev, false,
108 							      enable_bit, 0);
109 
110 	if (ret)
111 		goto ret_enable_store;
112 
113 	ret = size;
114 	*status = mode;
115 
116 ret_enable_store:
117 	mutex_unlock(&wt_lock);
118 
119 	return ret;
120 }
121 
workload_hint_enable_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)122 static ssize_t workload_hint_enable_store(struct device *dev, struct device_attribute *attr,
123 					  const char *buf, size_t size)
124 {
125 	return workload_hint_enable(dev, SOC_WT_PREDICTION_INT_ENABLE_BIT, &wt_enable,
126 				    attr, buf, size);
127 }
128 static DEVICE_ATTR_RW(workload_hint_enable);
129 
workload_slow_hint_enable_show(struct device * dev,struct device_attribute * attr,char * buf)130 static ssize_t workload_slow_hint_enable_show(struct device *dev, struct device_attribute *attr,
131 					       char *buf)
132 {
133 	return sysfs_emit(buf, "%d\n", wt_slow_enable);
134 }
135 
workload_slow_hint_enable_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)136 static ssize_t workload_slow_hint_enable_store(struct device *dev, struct device_attribute *attr,
137 					       const char *buf, size_t size)
138 {
139 	return workload_hint_enable(dev, SOC_WT_SLOW_PREDICTION_INT_ENABLE_BIT, &wt_slow_enable,
140 				    attr, buf, size);
141 }
142 static DEVICE_ATTR_RW(workload_slow_hint_enable);
143 
notification_delay_ms_show(struct device * dev,struct device_attribute * attr,char * buf)144 static ssize_t notification_delay_ms_show(struct device *dev,
145 					  struct device_attribute *attr,
146 					  char *buf)
147 {
148 	return sysfs_emit(buf, "%u\n", notify_delay_ms);
149 }
150 
notification_delay_ms_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)151 static ssize_t notification_delay_ms_store(struct device *dev,
152 					   struct device_attribute *attr,
153 					   const char *buf, size_t size)
154 {
155 	struct pci_dev *pdev = to_pci_dev(dev);
156 	u16 new_tw;
157 	int ret;
158 	u8 tm;
159 
160 	/*
161 	 * Time window register value:
162 	 * Formula: (1 + x/4) * power(2,y)
163 	 * x = 2 msbs, that is [30:29] y = 5 [28:24]
164 	 * in INTR_CONFIG register.
165 	 * The result will be in milli seconds.
166 	 * Here, just keep x = 0, and just change y.
167 	 * First round up the user value to power of 2 and
168 	 * then take log2, to get "y" value to program.
169 	 */
170 	ret = kstrtou16(buf, 10, &new_tw);
171 	if (ret)
172 		return ret;
173 
174 	if (!new_tw)
175 		return -EINVAL;
176 
177 	new_tw = roundup_pow_of_two(new_tw);
178 	tm = ilog2(new_tw);
179 	if (tm > 31)
180 		return -EINVAL;
181 
182 	mutex_lock(&wt_lock);
183 
184 	/* If the workload hint was already enabled, then update with the new delay */
185 	if (wt_enable)
186 		ret = processor_thermal_mbox_interrupt_config(pdev, true,
187 							      SOC_WT_PREDICTION_INT_ENABLE_BIT,
188 							      tm);
189 
190 	if (!ret) {
191 		ret = size;
192 		notify_delay = tm;
193 		notify_delay_ms = new_tw;
194 	}
195 
196 	mutex_unlock(&wt_lock);
197 
198 	return ret;
199 }
200 
201 static DEVICE_ATTR_RW(notification_delay_ms);
202 
workload_hint_attr_visible(struct kobject * kobj,struct attribute * attr,int unused)203 static umode_t workload_hint_attr_visible(struct kobject *kobj, struct attribute *attr, int unused)
204 {
205 	if (attr != &dev_attr_workload_slow_hint_enable.attr)
206 		return attr->mode;
207 
208 	switch (to_pci_dev(kobj_to_dev(kobj))->device) {
209 	case PCI_DEVICE_ID_INTEL_LNLM_THERMAL:
210 	case PCI_DEVICE_ID_INTEL_MTLP_THERMAL:
211 	case PCI_DEVICE_ID_INTEL_ARL_S_THERMAL:
212 		return 0;
213 	default:
214 		break;
215 	}
216 
217 	return attr->mode;
218 }
219 
220 static struct attribute *workload_hint_attrs[] = {
221 	&dev_attr_workload_type_index.attr,
222 	&dev_attr_workload_hint_enable.attr,
223 	&dev_attr_workload_slow_hint_enable.attr,
224 	&dev_attr_notification_delay_ms.attr,
225 	NULL
226 };
227 
228 static const struct attribute_group workload_hint_attribute_group = {
229 	.attrs = workload_hint_attrs,
230 	.name = "workload_hint",
231 	.is_visible = workload_hint_attr_visible
232 };
233 
234 /*
235  * Callback to check if the interrupt for prediction is active.
236  * Caution: Called from the interrupt context.
237  */
proc_thermal_check_wt_intr(struct proc_thermal_device * proc_priv)238 bool proc_thermal_check_wt_intr(struct proc_thermal_device *proc_priv)
239 {
240 	u64 int_status;
241 
242 	int_status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
243 	if (int_status & SOC_WT_PREDICTION_INT_ACTIVE)
244 		return true;
245 
246 	return false;
247 }
248 EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, "INT340X_THERMAL");
249 
250 /* Callback to notify user space */
proc_thermal_wt_intr_callback(struct pci_dev * pdev,struct proc_thermal_device * proc_priv)251 void proc_thermal_wt_intr_callback(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
252 {
253 	u64 status;
254 
255 	status = readq(proc_priv->mmio_base + SOC_WT_RES_INT_STATUS_OFFSET);
256 	if (!(status & SOC_WT_PREDICTION_INT_ACTIVE))
257 		return;
258 
259 	sysfs_notify(&pdev->dev.kobj, "workload_hint", "workload_type_index");
260 }
261 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, "INT340X_THERMAL");
262 
263 static bool workload_hint_created;
264 
proc_thermal_wt_hint_add(struct pci_dev * pdev,struct proc_thermal_device * proc_priv)265 int proc_thermal_wt_hint_add(struct pci_dev *pdev, struct proc_thermal_device *proc_priv)
266 {
267 	int ret;
268 
269 	ret = sysfs_create_group(&pdev->dev.kobj, &workload_hint_attribute_group);
270 	if (ret)
271 		return ret;
272 
273 	workload_hint_created = true;
274 
275 	return 0;
276 }
277 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, "INT340X_THERMAL");
278 
proc_thermal_wt_hint_remove(struct pci_dev * pdev)279 void proc_thermal_wt_hint_remove(struct pci_dev *pdev)
280 {
281 	mutex_lock(&wt_lock);
282 	if (wt_enable)
283 		processor_thermal_mbox_interrupt_config(pdev, false,
284 							SOC_WT_PREDICTION_INT_ENABLE_BIT,
285 							0);
286 	mutex_unlock(&wt_lock);
287 
288 	if (workload_hint_created)
289 		sysfs_remove_group(&pdev->dev.kobj, &workload_hint_attribute_group);
290 
291 	workload_hint_created = false;
292 }
293 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, "INT340X_THERMAL");
294 
295 MODULE_IMPORT_NS("INT340X_THERMAL");
296 MODULE_LICENSE("GPL");
297 MODULE_DESCRIPTION("Processor Thermal Work Load type hint Interface");
298