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