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 */
45 static u8 notify_delay = 0x0A;
46 static u16 notify_delay_ms = 1024;
47
48 static DEFINE_MUTEX(wt_lock);
49 static u8 wt_enable;
50
51 /* Show current predicted workload type index */
workload_type_index_show(struct device * dev,struct device_attribute * attr,char * buf)52 static 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
78 static DEVICE_ATTR_RO(workload_type_index);
79
workload_hint_enable_show(struct device * dev,struct device_attribute * attr,char * buf)80 static 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
workload_hint_enable_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)87 static 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
114 ret_enable_store:
115 mutex_unlock(&wt_lock);
116
117 return ret;
118 }
119
120 static DEVICE_ATTR_RW(workload_hint_enable);
121
notification_delay_ms_show(struct device * dev,struct device_attribute * attr,char * buf)122 static 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
notification_delay_ms_store(struct device * dev,struct device_attribute * attr,const char * buf,size_t size)129 static 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
179 static DEVICE_ATTR_RW(notification_delay_ms);
180
181 static 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
188 static 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 */
proc_thermal_check_wt_intr(struct proc_thermal_device * proc_priv)197 bool 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 }
207 EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr, INT340X_THERMAL);
208
209 /* Callback to notify user space */
proc_thermal_wt_intr_callback(struct pci_dev * pdev,struct proc_thermal_device * proc_priv)210 void 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 }
220 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback, INT340X_THERMAL);
221
222 static bool workload_hint_created;
223
proc_thermal_wt_hint_add(struct pci_dev * pdev,struct proc_thermal_device * proc_priv)224 int 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 }
236 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add, INT340X_THERMAL);
237
proc_thermal_wt_hint_remove(struct pci_dev * pdev)238 void 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 }
252 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove, INT340X_THERMAL);
253
254 MODULE_IMPORT_NS(INT340X_THERMAL);
255 MODULE_LICENSE("GPL");
256 MODULE_DESCRIPTION("Processor Thermal Work Load type hint Interface");
257