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