1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * AMD Platform Management Framework Driver - Metrics Support
4 *
5 * Copyright (c) 2026, Advanced Micro Devices, Inc.
6 * All Rights Reserved.
7 *
8 * Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
9 * Patil Rajesh Reddy <Patil.Reddy@amd.com>
10 */
11
12 #include <linux/array_size.h>
13 #include <linux/cleanup.h>
14 #include <linux/container_of.h>
15 #include <linux/device.h>
16 #include <linux/device/devres.h>
17 #include <linux/io.h>
18 #include <linux/ktime.h>
19 #include <linux/limits.h>
20 #include <linux/math64.h>
21 #include <linux/minmax.h>
22 #include <linux/string.h>
23 #include <linux/units.h>
24 #include <linux/wordpart.h>
25 #include <linux/workqueue.h>
26
27 #include "pmf.h"
28
29 static struct device *pmf_device;
30
amd_pmf_q10_acc_to_mW(u64 avg_acc)31 static u16 amd_pmf_q10_acc_to_mW(u64 avg_acc)
32 {
33 u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024) * MILLIWATT_PER_WATT;
34
35 return min_t(u16, val, U16_MAX);
36 }
37
amd_pmf_q10_acc_to_int(u64 avg_acc)38 static u16 amd_pmf_q10_acc_to_int(u64 avg_acc)
39 {
40 u64 val = DIV_U64_ROUND_CLOSEST(avg_acc, 1024);
41
42 return min_t(u16, val, U16_MAX);
43 }
44
amd_pmf_avg_acc_metric(u32 curr_counter,u32 prev_counter,u64 curr_value,u64 prev_value)45 static u64 amd_pmf_avg_acc_metric(u32 curr_counter, u32 prev_counter,
46 u64 curr_value, u64 prev_value)
47 {
48 u32 counter_diff;
49 u64 val_diff;
50
51 /* Check for counter reset */
52 if (curr_counter <= prev_counter)
53 return 0;
54
55 counter_diff = curr_counter - prev_counter;
56
57 if (curr_value < prev_value)
58 return 0;
59
60 val_diff = curr_value - prev_value;
61
62 return DIV_U64_ROUND_CLOSEST(val_diff, counter_diff);
63 }
64
amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev * dev)65 int amd_pmf_get_tbl_dram_addr(struct amd_pmf_dev *dev)
66 {
67 int ret;
68
69 ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_DRAM_ADDR, GET_CMD,
70 ARG_NONE, &dev->dram_addr.lo);
71 if (ret) {
72 dev_err(dev->dev, "Failed to get DRAM address: %d\n", ret);
73 return ret;
74 }
75
76 dev->metrics_table_phys = ((u64)dev->dram_addr.hi << 32) | dev->dram_addr.lo;
77 dev->mtable_size = dev->dram_addr.size;
78
79 if (dev->mtable_size != sizeof(dev->mtable_v3)) {
80 dev_err(dev->dev, "Metrics table size mismatch: got %u, expected %zu\n",
81 dev->dram_addr.size, sizeof(dev->mtable_v3));
82 return -EINVAL;
83 }
84
85 dev->metrics_table_virt = devm_ioremap(dev->dev, dev->metrics_table_phys, dev->mtable_size);
86 if (!dev->metrics_table_virt) {
87 dev_err(dev->dev, "Failed to map DRAM address for PMF metrics table\n");
88 dev->metrics_table_phys = 0;
89 return -ENOMEM;
90 }
91
92 return 0;
93 }
94
amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev * dev)95 static int amd_pmf_get_metrics_table_log_sample(struct amd_pmf_dev *dev)
96 {
97 int ret;
98
99 if (!dev->metrics_table_virt) {
100 dev_err(dev->dev, "Metrics DRAM not mapped\n");
101 return -EINVAL;
102 }
103
104 /* Send command to PMFW to update metrics table log sample */
105 ret = amd_pmf_send_cmd(dev, GET_1AH_M80H_METRICS_TABLE_LOG_SAMPLE, SET_CMD, ARG_NONE, NULL);
106 if (ret) {
107 dev_err(dev->dev, "Failed to request metrics log sample: %d\n", ret);
108 return ret;
109 }
110
111 return 0;
112 }
113
amd_pmf_get_metrics(struct work_struct * work)114 static void amd_pmf_get_metrics(struct work_struct *work)
115 {
116 struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
117 ktime_t time_elapsed_ms;
118 int socket_power;
119
120 guard(mutex)(&dev->update_mutex);
121
122 /* Transfer table contents */
123 memset(dev->buf, 0, sizeof(dev->m_table));
124 amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
125 memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
126
127 time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
128 /* Calculate the avg SoC power consumption */
129 socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
130
131 if (dev->amt_enabled) {
132 /* Apply the Auto Mode transition */
133 amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
134 }
135
136 if (dev->cnqf_enabled) {
137 /* Apply the CnQF transition */
138 amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
139 }
140
141 dev->start_time = ktime_to_ms(ktime_get());
142 schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
143 }
144
amd_pmf_set_dram_addr(struct amd_pmf_dev * dev,bool alloc_buffer)145 int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
146 {
147 u64 phys_addr;
148 u32 hi, low;
149
150 /* Get Metrics Table Address */
151 if (alloc_buffer) {
152 switch (dev->cpu_id) {
153 case AMD_CPU_ID_PS:
154 case AMD_CPU_ID_RMB:
155 dev->mtable_size = sizeof(dev->m_table);
156 break;
157 case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
158 case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
159 dev->mtable_size = sizeof(dev->m_table_v2);
160 break;
161 default:
162 dev_err(dev->dev, "Invalid CPU id: 0x%x\n", dev->cpu_id);
163 }
164
165 dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
166 if (!dev->buf)
167 return -ENOMEM;
168 }
169
170 phys_addr = virt_to_phys(dev->buf);
171 hi = upper_32_bits(phys_addr);
172 low = lower_32_bits(phys_addr);
173
174 amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
175 amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
176
177 return 0;
178 }
179
amd_pmf_init_metrics_table(struct amd_pmf_dev * dev)180 int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
181 {
182 int ret;
183
184 INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
185
186 ret = amd_pmf_set_dram_addr(dev, true);
187 if (ret)
188 return ret;
189
190 /*
191 * Start collecting the metrics data after a small delay
192 * or else, we might end up getting stale values from PMFW.
193 */
194 schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
195
196 return 0;
197 }
198
amd_pmf_set_device(struct device * p_device)199 void amd_pmf_set_device(struct device *p_device)
200 {
201 pmf_device = p_device;
202 }
203
is_npu_metrics_supported(struct amd_pmf_dev * pdev)204 static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
205 {
206 switch (pdev->cpu_id) {
207 case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
208 case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
209 case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
210 return 0;
211 default:
212 return -EOPNOTSUPP;
213 }
214 }
215
amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev * dev,struct amd_pmf_npu_metrics * data)216 static void amd_pmf_calculate_acc_npu_metrics(struct amd_pmf_dev *dev,
217 struct amd_pmf_npu_metrics *data)
218 {
219 struct amd_pmf_metrics_iod *curr, *prev;
220 u64 val;
221 int i;
222
223 curr = &dev->mtable_v3.iod;
224 prev = &dev->prev_metrics.iod;
225
226 val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
227 curr->npu_temp_acc, prev->npu_temp_acc);
228 data->npu_temp = amd_pmf_q10_acc_to_int(val);
229 val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
230 curr->npu_power_acc, prev->npu_power_acc);
231 data->npu_power = amd_pmf_q10_acc_to_mW(val);
232 val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
233 curr->npuhclk_freq_eff_acc,
234 prev->npuhclk_freq_eff_acc);
235 data->mpnpuclk_freq = amd_pmf_q10_acc_to_int(val);
236 val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
237 curr->aieclk_freq_eff_acc,
238 prev->aieclk_freq_eff_acc);
239 data->npuclk_freq = amd_pmf_q10_acc_to_int(val);
240
241 for (i = 0; i < ARRAY_SIZE(curr->npu_busy_acc); i++) {
242 val = amd_pmf_avg_acc_metric(curr->counter_acc, prev->counter_acc,
243 curr->npu_busy_acc[i],
244 prev->npu_busy_acc[i]);
245 data->npu_busy[i] = amd_pmf_q10_acc_to_int(val);
246 }
247 }
248
amd_pmf_get_smu_metrics(struct amd_pmf_dev * dev,struct amd_pmf_npu_metrics * data)249 static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
250 {
251 int ret, i;
252
253 guard(mutex)(&dev->metrics_mutex);
254
255 ret = is_npu_metrics_supported(dev);
256 if (ret)
257 return ret;
258
259 memset(data, 0, sizeof(*data));
260
261 switch (dev->cpu_id) {
262 case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
263 case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
264 ret = amd_pmf_set_dram_addr(dev, true);
265 if (ret)
266 return ret;
267
268 memset(dev->buf, 0, dev->mtable_size);
269
270 /* Send SMU command to get NPU metrics */
271 ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
272 if (ret) {
273 dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
274 return ret;
275 }
276
277 memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
278
279 data->npuclk_freq = dev->m_table_v2.npuclk_freq;
280 for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
281 data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
282 data->npu_power = dev->m_table_v2.npu_power;
283 data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
284 data->npu_reads = dev->m_table_v2.npu_reads;
285 data->npu_writes = dev->m_table_v2.npu_writes;
286 break;
287 case PCI_DEVICE_ID_AMD_1AH_M80H_ROOT:
288 ret = amd_pmf_get_metrics_table_log_sample(dev);
289 if (ret)
290 return ret;
291
292 memcpy_fromio(&dev->mtable_v3, dev->metrics_table_virt, sizeof(dev->mtable_v3));
293
294 /*
295 * Ignore the first sample, as previous metrics is uninitialized (zero)
296 * Metrics are calculated as:
297 * metrics = current_metrics - previous_metrics
298 * Skipping the initial sample ensures accurate delta calculations.
299 */
300 if (!dev->npu_metrics_have_prev) {
301 memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
302 dev->npu_metrics_have_prev = true;
303 return 0;
304 }
305
306 amd_pmf_calculate_acc_npu_metrics(dev, data);
307 memcpy(&dev->prev_metrics, &dev->mtable_v3, sizeof(dev->mtable_v3));
308 break;
309 }
310
311 return 0;
312 }
313
amd_pmf_get_npu_data(struct amd_pmf_npu_metrics * info)314 int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
315 {
316 struct amd_pmf_dev *pdev;
317
318 if (!info)
319 return -EINVAL;
320
321 if (!pmf_device)
322 return -ENODEV;
323
324 pdev = dev_get_drvdata(pmf_device);
325 if (!pdev)
326 return -ENODEV;
327
328 return amd_pmf_get_smu_metrics(pdev, info);
329 }
330 EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
331