1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. 4 * Copyright (c) 2022-2025, Qualcomm Innovation Center, Inc. All rights reserved. 5 */ 6 7 #include <linux/bitfield.h> 8 #include <linux/debugfs.h> 9 #include <linux/device.h> 10 #include <linux/io.h> 11 #include <linux/module.h> 12 #include <linux/of.h> 13 #include <linux/platform_device.h> 14 #include <linux/seq_file.h> 15 16 #include <linux/soc/qcom/qcom_aoss.h> 17 #include <linux/soc/qcom/smem.h> 18 #include <clocksource/arm_arch_timer.h> 19 20 #define RPM_DYNAMIC_ADDR 0x14 21 #define RPM_DYNAMIC_ADDR_MASK 0xFFFF 22 23 #define STAT_TYPE_OFFSET 0x0 24 #define COUNT_OFFSET 0x4 25 #define LAST_ENTERED_AT_OFFSET 0x8 26 #define LAST_EXITED_AT_OFFSET 0x10 27 #define ACCUMULATED_OFFSET 0x18 28 #define CLIENT_VOTES_OFFSET 0x20 29 30 #define DDR_STATS_MAGIC_KEY 0xA1157A75 31 #define DDR_STATS_MAX_NUM_MODES 20 32 #define DDR_STATS_MAGIC_KEY_ADDR 0x0 33 #define DDR_STATS_NUM_MODES_ADDR 0x4 34 #define DDR_STATS_ENTRY_START_ADDR 0x8 35 36 #define DDR_STATS_CP_IDX(data) FIELD_GET(GENMASK(4, 0), data) 37 #define DDR_STATS_LPM_NAME(data) FIELD_GET(GENMASK(7, 0), data) 38 #define DDR_STATS_TYPE(data) FIELD_GET(GENMASK(15, 8), data) 39 #define DDR_STATS_FREQ(data) FIELD_GET(GENMASK(31, 16), data) 40 41 static struct qmp *qcom_stats_qmp; 42 43 struct subsystem_data { 44 const char *name; 45 u32 smem_item; 46 u32 pid; 47 }; 48 49 static const struct subsystem_data subsystems[] = { 50 { "modem", 605, 1 }, 51 { "wpss", 605, 13 }, 52 { "adsp", 606, 2 }, 53 { "cdsp", 607, 5 }, 54 { "cdsp1", 607, 12 }, 55 { "gpdsp0", 607, 17 }, 56 { "gpdsp1", 607, 18 }, 57 { "slpi", 608, 3 }, 58 { "gpu", 609, 0 }, 59 { "display", 610, 0 }, 60 { "adsp_island", 613, 2 }, 61 { "slpi_island", 613, 3 }, 62 { "apss", 631, QCOM_SMEM_HOST_ANY }, 63 }; 64 65 struct stats_config { 66 size_t stats_offset; 67 size_t ddr_stats_offset; 68 size_t num_records; 69 bool appended_stats_avail; 70 bool dynamic_offset; 71 bool subsystem_stats_in_smem; 72 }; 73 74 struct ddr_stats_entry { 75 u32 name; 76 u32 count; 77 u64 duration; 78 }; 79 80 struct stats_data { 81 bool appended_stats_avail; 82 void __iomem *base; 83 }; 84 85 struct sleep_stats { 86 u32 stat_type; 87 u32 count; 88 u64 last_entered_at; 89 u64 last_exited_at; 90 u64 accumulated; 91 }; 92 93 struct appended_stats { 94 u32 client_votes; 95 u32 reserved[3]; 96 }; 97 98 static void qcom_print_stats(struct seq_file *s, const struct sleep_stats *stat) 99 { 100 u64 accumulated = stat->accumulated; 101 /* 102 * If a subsystem is in sleep when reading the sleep stats adjust 103 * the accumulated sleep duration to show actual sleep time. 104 */ 105 if (stat->last_entered_at > stat->last_exited_at) 106 accumulated += arch_timer_read_counter() - stat->last_entered_at; 107 108 seq_printf(s, "Count: %u\n", stat->count); 109 seq_printf(s, "Last Entered At: %llu\n", stat->last_entered_at); 110 seq_printf(s, "Last Exited At: %llu\n", stat->last_exited_at); 111 seq_printf(s, "Accumulated Duration: %llu\n", accumulated); 112 } 113 114 static int qcom_subsystem_sleep_stats_show(struct seq_file *s, void *unused) 115 { 116 struct subsystem_data *subsystem = s->private; 117 struct sleep_stats *stat; 118 119 /* Items are allocated lazily, so lookup pointer each time */ 120 stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL); 121 if (IS_ERR(stat)) 122 return 0; 123 124 qcom_print_stats(s, stat); 125 126 return 0; 127 } 128 129 static int qcom_soc_sleep_stats_show(struct seq_file *s, void *unused) 130 { 131 struct stats_data *d = s->private; 132 void __iomem *reg = d->base; 133 struct sleep_stats stat; 134 135 memcpy_fromio(&stat, reg, sizeof(stat)); 136 qcom_print_stats(s, &stat); 137 138 if (d->appended_stats_avail) { 139 struct appended_stats votes; 140 141 memcpy_fromio(&votes, reg + CLIENT_VOTES_OFFSET, sizeof(votes)); 142 seq_printf(s, "Client Votes: %#x\n", votes.client_votes); 143 } 144 145 return 0; 146 } 147 148 static void qcom_ddr_stats_print(struct seq_file *s, struct ddr_stats_entry *data) 149 { 150 u32 cp_idx; 151 152 /* 153 * DDR statistic have two different types of details encoded. 154 * (1) DDR LPM Stats 155 * (2) DDR Frequency Stats 156 * 157 * The name field have details like which type of DDR stat (bits 8:15) 158 * along with other details as explained below 159 * 160 * In case of DDR LPM stat, name field will be encoded as, 161 * Bits - Meaning 162 * 0:7 - DDR LPM name, can be of 0xd4, 0xd3, 0x11 and 0xd0. 163 * 8:15 - 0x0 (indicates its a LPM stat) 164 * 16:31 - Unused 165 * 166 * In case of DDR FREQ stats, name field will be encoded as, 167 * Bits - Meaning 168 * 0:4 - DDR Clock plan index (CP IDX) 169 * 5:7 - Unused 170 * 8:15 - 0x1 (indicates its Freq stat) 171 * 16:31 - Frequency value in Mhz 172 */ 173 switch (DDR_STATS_TYPE(data->name)) { 174 case 0: 175 seq_printf(s, "DDR LPM Stat Name:0x%lx\tcount:%u\tDuration (ticks):%llu\n", 176 DDR_STATS_LPM_NAME(data->name), data->count, data->duration); 177 break; 178 case 1: 179 if (!data->count || !DDR_STATS_FREQ(data->name)) 180 return; 181 182 cp_idx = DDR_STATS_CP_IDX(data->name); 183 seq_printf(s, "DDR Freq %luMhz:\tCP IDX:%u\tcount:%u\tDuration (ticks):%llu\n", 184 DDR_STATS_FREQ(data->name), cp_idx, data->count, data->duration); 185 break; 186 } 187 } 188 189 static int qcom_ddr_stats_show(struct seq_file *s, void *d) 190 { 191 struct ddr_stats_entry data[DDR_STATS_MAX_NUM_MODES]; 192 void __iomem *reg = (void __iomem *)s->private; 193 u32 entry_count; 194 int i, ret; 195 196 entry_count = readl_relaxed(reg + DDR_STATS_NUM_MODES_ADDR); 197 if (entry_count > DDR_STATS_MAX_NUM_MODES) 198 return -EINVAL; 199 200 if (qcom_stats_qmp) { 201 /* 202 * Recent SoCs (SM8450 onwards) do not have duration field 203 * populated from boot up onwards for both DDR LPM Stats 204 * and DDR Frequency Stats. 205 * 206 * Send QMP message to Always on processor which will 207 * populate duration field into MSG RAM area. 208 * 209 * Sent every time to read latest data. 210 */ 211 ret = qmp_send(qcom_stats_qmp, "{class: ddr, action: freqsync}"); 212 if (ret) 213 return ret; 214 } 215 216 reg += DDR_STATS_ENTRY_START_ADDR; 217 memcpy_fromio(data, reg, sizeof(struct ddr_stats_entry) * entry_count); 218 219 for (i = 0; i < entry_count; i++) 220 qcom_ddr_stats_print(s, &data[i]); 221 222 return 0; 223 } 224 225 DEFINE_SHOW_ATTRIBUTE(qcom_soc_sleep_stats); 226 DEFINE_SHOW_ATTRIBUTE(qcom_subsystem_sleep_stats); 227 DEFINE_SHOW_ATTRIBUTE(qcom_ddr_stats); 228 229 static void qcom_create_ddr_stat_files(struct dentry *root, void __iomem *reg, 230 const struct stats_config *config) 231 { 232 u32 key; 233 234 if (!config->ddr_stats_offset) 235 return; 236 237 key = readl_relaxed(reg + config->ddr_stats_offset + DDR_STATS_MAGIC_KEY_ADDR); 238 if (key == DDR_STATS_MAGIC_KEY) 239 debugfs_create_file("ddr_stats", 0400, root, 240 (__force void *)reg + config->ddr_stats_offset, 241 &qcom_ddr_stats_fops); 242 } 243 244 static void qcom_create_soc_sleep_stat_files(struct dentry *root, void __iomem *reg, 245 struct stats_data *d, 246 const struct stats_config *config) 247 { 248 char stat_type[sizeof(u32) + 1] = {0}; 249 size_t stats_offset = config->stats_offset; 250 u32 offset = 0, type; 251 int i, j; 252 253 /* 254 * On RPM targets, stats offset location is dynamic and changes from target 255 * to target and sometimes from build to build for same target. 256 * 257 * In such cases the dynamic address is present at 0x14 offset from base 258 * address in devicetree. The last 16bits indicates the stats_offset. 259 */ 260 if (config->dynamic_offset) { 261 stats_offset = readl(reg + RPM_DYNAMIC_ADDR); 262 stats_offset &= RPM_DYNAMIC_ADDR_MASK; 263 } 264 265 for (i = 0; i < config->num_records; i++) { 266 d[i].base = reg + offset + stats_offset; 267 268 /* 269 * Read the low power mode name and create debugfs file for it. 270 * The names read could be of below, 271 * (may change depending on low power mode supported). 272 * For rpmh-sleep-stats: "aosd", "cxsd" and "ddr". 273 * For rpm-sleep-stats: "vmin" and "vlow". 274 */ 275 type = readl(d[i].base); 276 for (j = 0; j < sizeof(u32); j++) { 277 stat_type[j] = type & 0xff; 278 type = type >> 8; 279 } 280 strim(stat_type); 281 debugfs_create_file(stat_type, 0400, root, &d[i], 282 &qcom_soc_sleep_stats_fops); 283 284 offset += sizeof(struct sleep_stats); 285 if (d[i].appended_stats_avail) 286 offset += sizeof(struct appended_stats); 287 } 288 } 289 290 static void qcom_create_subsystem_stat_files(struct dentry *root, 291 const struct stats_config *config) 292 { 293 int i; 294 295 if (!config->subsystem_stats_in_smem) 296 return; 297 298 for (i = 0; i < ARRAY_SIZE(subsystems); i++) 299 debugfs_create_file(subsystems[i].name, 0400, root, (void *)&subsystems[i], 300 &qcom_subsystem_sleep_stats_fops); 301 } 302 303 static int qcom_stats_probe(struct platform_device *pdev) 304 { 305 void __iomem *reg; 306 struct dentry *root; 307 const struct stats_config *config; 308 struct stats_data *d; 309 int i; 310 311 config = device_get_match_data(&pdev->dev); 312 if (!config) 313 return -ENODEV; 314 315 reg = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 316 if (IS_ERR(reg)) 317 return -ENOMEM; 318 319 d = devm_kcalloc(&pdev->dev, config->num_records, 320 sizeof(*d), GFP_KERNEL); 321 if (!d) 322 return -ENOMEM; 323 324 for (i = 0; i < config->num_records; i++) 325 d[i].appended_stats_avail = config->appended_stats_avail; 326 /* 327 * QMP is used for DDR stats syncing to MSG RAM for recent SoCs (SM8450 onwards). 328 * The prior SoCs do not need QMP handle as the required stats are already present 329 * in MSG RAM, provided the DDR_STATS_MAGIC_KEY matches. 330 */ 331 qcom_stats_qmp = qmp_get(&pdev->dev); 332 if (IS_ERR(qcom_stats_qmp)) { 333 /* We ignore error if QMP is not defined/needed */ 334 if (!of_property_present(pdev->dev.of_node, "qcom,qmp")) 335 qcom_stats_qmp = NULL; 336 else if (PTR_ERR(qcom_stats_qmp) == -EPROBE_DEFER) 337 return -EPROBE_DEFER; 338 else 339 return PTR_ERR(qcom_stats_qmp); 340 } 341 342 root = debugfs_create_dir("qcom_stats", NULL); 343 344 qcom_create_subsystem_stat_files(root, config); 345 qcom_create_soc_sleep_stat_files(root, reg, d, config); 346 qcom_create_ddr_stat_files(root, reg, config); 347 348 platform_set_drvdata(pdev, root); 349 350 device_set_pm_not_required(&pdev->dev); 351 352 return 0; 353 } 354 355 static void qcom_stats_remove(struct platform_device *pdev) 356 { 357 struct dentry *root = platform_get_drvdata(pdev); 358 359 debugfs_remove_recursive(root); 360 } 361 362 static const struct stats_config rpm_data = { 363 .stats_offset = 0, 364 .num_records = 2, 365 .appended_stats_avail = true, 366 .dynamic_offset = true, 367 .subsystem_stats_in_smem = false, 368 }; 369 370 /* Older RPM firmwares have the stats at a fixed offset instead */ 371 static const struct stats_config rpm_data_dba0 = { 372 .stats_offset = 0xdba0, 373 .num_records = 2, 374 .appended_stats_avail = true, 375 .dynamic_offset = false, 376 .subsystem_stats_in_smem = false, 377 }; 378 379 static const struct stats_config rpmh_data_sdm845 = { 380 .stats_offset = 0x48, 381 .num_records = 2, 382 .appended_stats_avail = false, 383 .dynamic_offset = false, 384 .subsystem_stats_in_smem = true, 385 }; 386 387 static const struct stats_config rpmh_data = { 388 .stats_offset = 0x48, 389 .ddr_stats_offset = 0xb8, 390 .num_records = 3, 391 .appended_stats_avail = false, 392 .dynamic_offset = false, 393 .subsystem_stats_in_smem = true, 394 }; 395 396 static const struct of_device_id qcom_stats_table[] = { 397 { .compatible = "qcom,apq8084-rpm-stats", .data = &rpm_data_dba0 }, 398 { .compatible = "qcom,msm8226-rpm-stats", .data = &rpm_data_dba0 }, 399 { .compatible = "qcom,msm8916-rpm-stats", .data = &rpm_data_dba0 }, 400 { .compatible = "qcom,msm8974-rpm-stats", .data = &rpm_data_dba0 }, 401 { .compatible = "qcom,rpm-stats", .data = &rpm_data }, 402 { .compatible = "qcom,rpmh-stats", .data = &rpmh_data }, 403 { .compatible = "qcom,sdm845-rpmh-stats", .data = &rpmh_data_sdm845 }, 404 { } 405 }; 406 MODULE_DEVICE_TABLE(of, qcom_stats_table); 407 408 static struct platform_driver qcom_stats = { 409 .probe = qcom_stats_probe, 410 .remove = qcom_stats_remove, 411 .driver = { 412 .name = "qcom_stats", 413 .of_match_table = qcom_stats_table, 414 }, 415 }; 416 417 static int __init qcom_stats_init(void) 418 { 419 return platform_driver_register(&qcom_stats); 420 } 421 late_initcall(qcom_stats_init); 422 423 static void __exit qcom_stats_exit(void) 424 { 425 platform_driver_unregister(&qcom_stats); 426 } 427 module_exit(qcom_stats_exit) 428 429 MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Stats driver"); 430 MODULE_LICENSE("GPL v2"); 431