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 -EIO; 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 const struct sleep_stats *stat; 174 int i; 175 176 if (!config->subsystem_stats_in_smem) 177 return; 178 179 for (i = 0; i < ARRAY_SIZE(subsystems); i++) { 180 stat = qcom_smem_get(subsystems[i].pid, subsystems[i].smem_item, NULL); 181 if (IS_ERR(stat)) 182 continue; 183 184 debugfs_create_file(subsystems[i].name, 0400, root, (void *)&subsystems[i], 185 &qcom_subsystem_sleep_stats_fops); 186 } 187 } 188 189 static int qcom_stats_probe(struct platform_device *pdev) 190 { 191 void __iomem *reg; 192 struct dentry *root; 193 const struct stats_config *config; 194 struct stats_data *d; 195 int i; 196 197 config = device_get_match_data(&pdev->dev); 198 if (!config) 199 return -ENODEV; 200 201 reg = devm_platform_get_and_ioremap_resource(pdev, 0, NULL); 202 if (IS_ERR(reg)) 203 return -ENOMEM; 204 205 d = devm_kcalloc(&pdev->dev, config->num_records, 206 sizeof(*d), GFP_KERNEL); 207 if (!d) 208 return -ENOMEM; 209 210 for (i = 0; i < config->num_records; i++) 211 d[i].appended_stats_avail = config->appended_stats_avail; 212 213 root = debugfs_create_dir("qcom_stats", NULL); 214 215 qcom_create_subsystem_stat_files(root, config); 216 qcom_create_soc_sleep_stat_files(root, reg, d, config); 217 218 platform_set_drvdata(pdev, root); 219 220 return 0; 221 } 222 223 static int qcom_stats_remove(struct platform_device *pdev) 224 { 225 struct dentry *root = platform_get_drvdata(pdev); 226 227 debugfs_remove_recursive(root); 228 229 return 0; 230 } 231 232 static const struct stats_config rpm_data = { 233 .stats_offset = 0, 234 .num_records = 2, 235 .appended_stats_avail = true, 236 .dynamic_offset = true, 237 .subsystem_stats_in_smem = false, 238 }; 239 240 /* Older RPM firmwares have the stats at a fixed offset instead */ 241 static const struct stats_config rpm_data_dba0 = { 242 .stats_offset = 0xdba0, 243 .num_records = 2, 244 .appended_stats_avail = true, 245 .dynamic_offset = false, 246 .subsystem_stats_in_smem = false, 247 }; 248 249 static const struct stats_config rpmh_data = { 250 .stats_offset = 0x48, 251 .num_records = 3, 252 .appended_stats_avail = false, 253 .dynamic_offset = false, 254 .subsystem_stats_in_smem = true, 255 }; 256 257 static const struct of_device_id qcom_stats_table[] = { 258 { .compatible = "qcom,apq8084-rpm-stats", .data = &rpm_data_dba0 }, 259 { .compatible = "qcom,msm8226-rpm-stats", .data = &rpm_data_dba0 }, 260 { .compatible = "qcom,msm8916-rpm-stats", .data = &rpm_data_dba0 }, 261 { .compatible = "qcom,msm8974-rpm-stats", .data = &rpm_data_dba0 }, 262 { .compatible = "qcom,rpm-stats", .data = &rpm_data }, 263 { .compatible = "qcom,rpmh-stats", .data = &rpmh_data }, 264 { } 265 }; 266 MODULE_DEVICE_TABLE(of, qcom_stats_table); 267 268 static struct platform_driver qcom_stats = { 269 .probe = qcom_stats_probe, 270 .remove = qcom_stats_remove, 271 .driver = { 272 .name = "qcom_stats", 273 .of_match_table = qcom_stats_table, 274 }, 275 }; 276 277 static int __init qcom_stats_init(void) 278 { 279 return platform_driver_register(&qcom_stats); 280 } 281 late_initcall(qcom_stats_init); 282 283 static void __exit qcom_stats_exit(void) 284 { 285 platform_driver_unregister(&qcom_stats); 286 } 287 module_exit(qcom_stats_exit) 288 289 MODULE_DESCRIPTION("Qualcomm Technologies, Inc. (QTI) Stats driver"); 290 MODULE_LICENSE("GPL v2"); 291