1 // SPDX-License-Identifier: GPL-2.0-only
3 * processor thermal device interface for reading workload type hints
4 * from the user space. The hints are provided by the firmware.
7 * When user space enables workload type prediction:
9 * Configure notification delay
10 * Enable processor thermal device interrupt
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.
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
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
28 * Copyright (c) 2023, Intel Corporation.
31 #include <linux/bitfield.h>
32 #include <linux/pci.h>
33 #include "processor_thermal_device.h"
35 #define SOC_WT GENMASK_ULL(47, 40)
37 #define SOC_WT_PREDICTION_INT_ENABLE_BIT 23
39 #define SOC_WT_PREDICTION_INT_ACTIVE BIT(2)
42 * Closest possible to 1 Second is 1024 ms with programmed time delay
45 static u8 notify_delay
= 0x0A;
46 static u16 notify_delay_ms
= 1024;
48 static DEFINE_MUTEX(wt_lock
);
51 /* Show current predicted workload type index */
52 static ssize_t
workload_type_index_show(struct device
*dev
,
53 struct device_attribute
*attr
,
56 struct proc_thermal_device
*proc_priv
;
57 struct pci_dev
*pdev
= to_pci_dev(dev
);
63 mutex_unlock(&wt_lock
);
67 proc_priv
= pci_get_drvdata(pdev
);
69 status
= readq(proc_priv
->mmio_base
+ SOC_WT_RES_INT_STATUS_OFFSET
);
71 mutex_unlock(&wt_lock
);
73 wt
= FIELD_GET(SOC_WT
, status
);
75 return sysfs_emit(buf
, "%d\n", wt
);
78 static DEVICE_ATTR_RO(workload_type_index
);
80 static ssize_t
workload_hint_enable_show(struct device
*dev
,
81 struct device_attribute
*attr
,
84 return sysfs_emit(buf
, "%d\n", wt_enable
);
87 static ssize_t
workload_hint_enable_store(struct device
*dev
,
88 struct device_attribute
*attr
,
89 const char *buf
, size_t size
)
91 struct pci_dev
*pdev
= to_pci_dev(dev
);
95 if (kstrtou8(buf
, 10, &mode
) || mode
> 1)
101 ret
= processor_thermal_mbox_interrupt_config(pdev
, true,
102 SOC_WT_PREDICTION_INT_ENABLE_BIT
,
105 ret
= processor_thermal_mbox_interrupt_config(pdev
, false,
106 SOC_WT_PREDICTION_INT_ENABLE_BIT
, 0);
109 goto ret_enable_store
;
115 mutex_unlock(&wt_lock
);
120 static DEVICE_ATTR_RW(workload_hint_enable
);
122 static ssize_t
notification_delay_ms_show(struct device
*dev
,
123 struct device_attribute
*attr
,
126 return sysfs_emit(buf
, "%u\n", notify_delay_ms
);
129 static ssize_t
notification_delay_ms_store(struct device
*dev
,
130 struct device_attribute
*attr
,
131 const char *buf
, size_t size
)
133 struct pci_dev
*pdev
= to_pci_dev(dev
);
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.
148 ret
= kstrtou16(buf
, 10, &new_tw
);
155 new_tw
= roundup_pow_of_two(new_tw
);
160 mutex_lock(&wt_lock
);
162 /* If the workload hint was already enabled, then update with the new delay */
164 ret
= processor_thermal_mbox_interrupt_config(pdev
, true,
165 SOC_WT_PREDICTION_INT_ENABLE_BIT
,
171 notify_delay_ms
= new_tw
;
174 mutex_unlock(&wt_lock
);
179 static DEVICE_ATTR_RW(notification_delay_ms
);
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
,
188 static const struct attribute_group workload_hint_attribute_group
= {
189 .attrs
= workload_hint_attrs
,
190 .name
= "workload_hint"
194 * Callback to check if the interrupt for prediction is active.
195 * Caution: Called from the interrupt context.
197 bool proc_thermal_check_wt_intr(struct proc_thermal_device
*proc_priv
)
201 int_status
= readq(proc_priv
->mmio_base
+ SOC_WT_RES_INT_STATUS_OFFSET
);
202 if (int_status
& SOC_WT_PREDICTION_INT_ACTIVE
)
207 EXPORT_SYMBOL_NS_GPL(proc_thermal_check_wt_intr
, "INT340X_THERMAL");
209 /* Callback to notify user space */
210 void proc_thermal_wt_intr_callback(struct pci_dev
*pdev
, struct proc_thermal_device
*proc_priv
)
214 status
= readq(proc_priv
->mmio_base
+ SOC_WT_RES_INT_STATUS_OFFSET
);
215 if (!(status
& SOC_WT_PREDICTION_INT_ACTIVE
))
218 sysfs_notify(&pdev
->dev
.kobj
, "workload_hint", "workload_type_index");
220 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_intr_callback
, "INT340X_THERMAL");
222 static bool workload_hint_created
;
224 int proc_thermal_wt_hint_add(struct pci_dev
*pdev
, struct proc_thermal_device
*proc_priv
)
228 ret
= sysfs_create_group(&pdev
->dev
.kobj
, &workload_hint_attribute_group
);
232 workload_hint_created
= true;
236 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_add
, "INT340X_THERMAL");
238 void proc_thermal_wt_hint_remove(struct pci_dev
*pdev
)
240 mutex_lock(&wt_lock
);
242 processor_thermal_mbox_interrupt_config(pdev
, false,
243 SOC_WT_PREDICTION_INT_ENABLE_BIT
,
245 mutex_unlock(&wt_lock
);
247 if (workload_hint_created
)
248 sysfs_remove_group(&pdev
->dev
.kobj
, &workload_hint_attribute_group
);
250 workload_hint_created
= false;
252 EXPORT_SYMBOL_NS_GPL(proc_thermal_wt_hint_remove
, "INT340X_THERMAL");
254 MODULE_IMPORT_NS("INT340X_THERMAL");
255 MODULE_LICENSE("GPL");
256 MODULE_DESCRIPTION("Processor Thermal Work Load type hint Interface");