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