1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. 4 */ 5 6 #include <linux/debugfs.h> 7 #include <linux/device.h> 8 #include <linux/io.h> 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <linux/platform_device.h> 12 #include <linux/seq_file.h> 13 14 #include <linux/soc/qcom/smem.h> 15 #include <clocksource/arm_arch_timer.h> 16 17 #define RPM_DYNAMIC_ADDR 0x14 18 #define RPM_DYNAMIC_ADDR_MASK 0xFFFF 19 20 #define STAT_TYPE_OFFSET 0x0 21 #define COUNT_OFFSET 0x4 22 #define LAST_ENTERED_AT_OFFSET 0x8 23 #define LAST_EXITED_AT_OFFSET 0x10 24 #define ACCUMULATED_OFFSET 0x18 25 #define CLIENT_VOTES_OFFSET 0x20 26 27 struct subsystem_data { 28 const char *name; 29 u32 smem_item; 30 u32 pid; 31 }; 32 33 static const struct subsystem_data subsystems[] = { 34 { "modem", 605, 1 }, 35 { "wpss", 605, 13 }, 36 { "adsp", 606, 2 }, 37 { "cdsp", 607, 5 }, 38 { "cdsp1", 607, 12 }, 39 { "gpdsp0", 607, 17 }, 40 { "gpdsp1", 607, 18 }, 41 { "slpi", 608, 3 }, 42 { "gpu", 609, 0 }, 43 { "display", 610, 0 }, 44 { "adsp_island", 613, 2 }, 45 { "slpi_island", 613, 3 }, 46 { "apss", 631, QCOM_SMEM_HOST_ANY }, 47 }; 48 49 struct stats_config { 50 size_t stats_offset; 51 size_t num_records; 52 bool appended_stats_avail; 53 bool dynamic_offset; 54 bool subsystem_stats_in_smem; 55 }; 56 57 struct stats_data { 58 bool appended_stats_avail; 59 void __iomem *base; 60 }; 61 62 struct sleep_stats { 63 u32 stat_type; 64 u32 count; 65 u64 last_entered_at; 66 u64 last_exited_at; 67 u64 accumulated; 68 }; 69 70 struct appended_stats { 71 u32 client_votes; 72 u32 reserved[3]; 73 }; 74 75 static void qcom_print_stats(struct seq_file *s, const struct sleep_stats *stat) 76 { 77 u64 accumulated = stat->accumulated; 78 /* 79 * If a subsystem is in sleep when reading the sleep stats adjust 80 * the accumulated sleep duration to show actual sleep time. 81 */ 82 if (stat->last_entered_at > stat->last_exited_at) 83 accumulated += arch_timer_read_counter() - stat->last_entered_at; 84 85 seq_printf(s, "Count: %u\n", stat->count); 86 seq_printf(s, "Last Entered At: %llu\n", stat->last_entered_at); 87 seq_printf(s, "Last Exited At: %llu\n", stat->last_exited_at); 88 seq_printf(s, "Accumulated Duration: %llu\n", accumulated); 89 } 90 91 static int qcom_subsystem_sleep_stats_show(struct seq_file *s, void *unused) 92 { 93 struct subsystem_data *subsystem = s->private; 94 struct sleep_stats *stat; 95 96 /* Items are allocated lazily, so lookup pointer each time */ 97 stat = qcom_smem_get(subsystem->pid, subsystem->smem_item, NULL); 98 if (IS_ERR(stat)) 99 return 0; 100 101 qcom_print_stats(s, stat); 102 103 return 0; 104 } 105 106 static int qcom_soc_sleep_stats_show(struct seq_file *s, void *unused) 107 { 108 struct stats_data *d = s->private; 109 void __iomem *reg = d->base; 110 struct sleep_stats stat; 111 112 memcpy_fromio(&stat, reg, sizeof(stat)); 113 qcom_print_stats(s, &stat); 114 115 if (d->appended_stats_avail) { 116 struct appended_stats votes; 117 118 memcpy_fromio(&votes, reg + CLIENT_VOTES_OFFSET, sizeof(votes)); 119 seq_printf(s, "Client Votes: %#x\n", votes.client_votes); 120 } 121 122 return 0; 123 } 124 125 DEFINE_SHOW_ATTRIBUTE(qcom_soc_sleep_stats); 126 DEFINE_SHOW_ATTRIBUTE(qcom_subsystem_sleep_stats); 127 128 static void qcom_create_soc_sleep_stat_files(struct dentry *root, void __iomem *reg, 129 struct stats_data *d, 130 const struct stats_config *config) 131 { 132 char stat_type[sizeof(u32) + 1] = {0}; 133 size_t stats_offset = config->stats_offset; 134 u32 offset = 0, type; 135 int i, j; 136 137 /* 138 * On RPM targets, stats offset location is dynamic and changes from target 139 * to target and sometimes from build to build for same target. 140 * 141 * In such cases the dynamic address is present at 0x14 offset from base 142 * address in devicetree. The last 16bits indicates the stats_offset. 143 */ 144 if (config->dynamic_offset) { 145 stats_offset = readl(reg + RPM_DYNAMIC_ADDR); 146 stats_offset &= RPM_DYNAMIC_ADDR_MASK; 147 } 148 149 for (i = 0; i < config->num_records; i++) { 150 d[i].base = reg + offset + stats_offset; 151 152 /* 153 * Read the low power mode name and create debugfs file for it. 154 * The names read could be of below, 155 * (may change depending on low power mode supported). 156 * For rpmh-sleep-stats: "aosd", "cxsd" and "ddr". 157 * For rpm-sleep-stats: "vmin" and "vlow". 158 */ 159 type = readl(d[i].base); 160 for (j = 0; j < sizeof(u32); j++) { 161 stat_type[j] = type & 0xff; 162 type = type >> 8; 163 } 164 strim(stat_type); 165 debugfs_create_file(stat_type, 0400, root, &d[i], 166 &qcom_soc_sleep_stats_fops); 167 168 offset += sizeof(struct sleep_stats); 169 if (d[i].appended_stats_avail) 170 offset += sizeof(struct appended_stats); 171 } 172 } 173 174 static void qcom_create_subsystem_stat_files(struct dentry *root, 175 const struct stats_config *config) 176 { 177 int i; 178 179 if (!config->subsystem_stats_in_smem) 180 return; 181 182 for (i = 0; i < ARRAY_SIZE(subsystems); i++) 183 debugfs_create_file(subsystems[i].name, 0400, root, (void *)&subsystems[i], 184 &qcom_subsystem_sleep_stats_fops); 185 } 186 187 static int qcom_stats_probe(struct platform_device *pdev) 188 { 189 void __iomem *reg; 190 struct dentry *root; 191 const struct stats_config *config; 192 struct stats_data *d; 193 int i; 194 195 config = device_get_match_data(&pdev->dev); 196 if (!config) 197 return -ENODEV; 198 199 reg = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 200 if (IS_ERR(reg)) 201 return -ENOMEM; 202 203 d = devm_kcalloc(&pdev->dev, config->num_records, 204 sizeof(*d), GFP_KERNEL); 205 if (!d) 206 return -ENOMEM; 207 208 for (i = 0; i < config->num_records; i++) 209 d[i].appended_stats_avail = config->appended_stats_avail; 210 211 root = debugfs_create_dir("qcom_stats", NULL); 212 213 qcom_create_subsystem_stat_files(root, config); 214 qcom_create_soc_sleep_stat_files(root, reg, d, config); 215 216 platform_set_drvdata(pdev, root); 217 218 device_set_pm_not_required(&pdev->dev); 219 220 return 0; 221 } 222 223 static void qcom_stats_remove(struct platform_device *pdev) 224 { 225 struct dentry *root = platform_get_drvdata(pdev); 226 227 debugfs_remove_recursive(root); 228 } 229 230 static const struct stats_config rpm_data = { 231 .stats_offset = 0, 232 .num_records = 2, 233 .appended_stats_avail = true, 234 .dynamic_offset = true, 235 .subsystem_stats_in_smem = false, 236 }; 237 238 /* Older RPM firmwares have the stats at a fixed offset instead */ 239 static const struct stats_config rpm_data_dba0 = { 240 .stats_offset = 0xdba0, 241 .num_records = 2, 242 .appended_stats_avail = true, 243 .dynamic_offset = false, 244 .subsystem_stats_in_smem = false, 245 }; 246 247 static const struct stats_config rpmh_data_sdm845 = { 248 .stats_offset = 0x48, 249 .num_records = 2, 250 .appended_stats_avail = false, 251 .dynamic_offset = false, 252 .subsystem_stats_in_smem = true, 253 }; 254 255 static const struct stats_config rpmh_data = { 256 .stats_offset = 0x48, 257 .num_records = 3, 258 .appended_stats_avail = false, 259 .dynamic_offset = false, 260 .subsystem_stats_in_smem = true, 261 }; 262 263 static const struct of_device_id qcom_stats_table[] = { 264 { .compatible = "qcom,apq8084-rpm-stats", .data = &rpm_data_dba0 }, 265 { .compatible = "qcom,msm8226-rpm-stats", .data = &rpm_data_dba0 }, 266 { .compatible = "qcom,msm8916-rpm-stats", .data = &rpm_data_dba0 }, 267 { .compatible = "qcom,msm8974-rpm-stats", .data = &rpm_data_dba0 }, 268 { .compatible = "qcom,rpm-stats", .data = &rpm_data }, 269 { .compatible = "qcom,rpmh-stats", .data = &rpmh_data }, 270 { .compatible = "qcom,sdm845-rpmh-stats", .data = &rpmh_data_sdm845 }, 271 { } 272 }; 273 MODULE_DEVICE_TABLE(of, qcom_stats_table); 274 275 static struct platform_driver qcom_stats = { 276 .probe = qcom_stats_probe, 277 .remove = qcom_stats_remove, 278 .driver = { 279 .name = "qcom_stats", 280 .of_match_table = qcom_stats_table, 281 }, 282 }; 283 284 static int __init qcom_stats_init(void) 285 { 286 return platform_driver_register(&qcom_stats); 287 } 288 late_initcall(qcom_stats_init); 289 290 static void __exit qcom_stats_exit(void) 291 { 292 platform_driver_unregister(&qcom_stats); 293 } 294 module_exit(qcom_stats_exit) 295 296 MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Stats driver"); 297 MODULE_LICENSE("GPL v2"); 298