/* Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * */ #define pr_fmt(fmt) "%s: " fmt, KBUILD_MODNAME #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpmh_master_stat.h" #define UNIT_DIST 0x14 #define REG_VALID 0x0 #define REG_DATA_LO 0x4 #define REG_DATA_HI 0x8 #define GET_ADDR(REG, UNIT_NO) (REG + (UNIT_DIST * UNIT_NO)) enum master_smem_id { MPSS = 605, ADSP, CDSP, SLPI, GPU, DISPLAY, }; enum master_pid { PID_APSS = 0, PID_MPSS = 1, PID_ADSP = 2, PID_SLPI = 3, PID_CDSP = 5, PID_GPU = PID_APSS, PID_DISPLAY = PID_APSS, }; enum profile_data { POWER_DOWN_START, POWER_UP_END, POWER_DOWN_END, POWER_UP_START, NUM_UNIT, }; struct msm_rpmh_master_data { char *master_name; enum master_smem_id smem_id; enum master_pid pid; }; static const struct msm_rpmh_master_data rpmh_masters[] = { {"MPSS", MPSS, PID_MPSS}, {"ADSP", ADSP, PID_ADSP}, {"CDSP", CDSP, PID_CDSP}, {"SLPI", SLPI, PID_SLPI}, {"GPU", GPU, PID_GPU}, {"DISPLAY", DISPLAY, PID_DISPLAY}, }; struct msm_rpmh_master_stats { uint32_t version_id; uint32_t counts; uint64_t last_entered; uint64_t last_exited; uint64_t accumulated_duration; }; struct msm_rpmh_profile_unit { uint64_t value; uint64_t valid; }; struct rpmh_master_stats_prv_data { struct kobj_attribute ka; struct kobject *kobj; }; static struct msm_rpmh_master_stats apss_master_stats; static void __iomem *rpmh_unit_base; static DEFINE_MUTEX(rpmh_stats_mutex); static void msm_rpmh_master_stats_print_data(struct seq_file *s, struct msm_rpmh_master_stats *record, const char *name) { uint64_t temp_accumulated_duration = record->accumulated_duration; /* * If a master is in sleep when reading the sleep stats from SMEM * adjust the accumulated sleep duration to show actual sleep time. * This ensures that the displayed stats are real when used for * the purpose of computing battery utilization. */ if (record->last_entered > record->last_exited) temp_accumulated_duration += (arch_counter_get_cntvct() - record->last_entered); seq_printf(s, "%s\n\tVersion:0x%x\n" "\tSleep Count:0x%x\n" "\tSleep Last Entered At:0x%llx\n" "\tSleep Last Exited At:0x%llx\n" "\tSleep Accumulated Duration:0x%llx\n\n", name, record->version_id, record->counts, record->last_entered, record->last_exited, temp_accumulated_duration); } static int rpmh_master_stats_show(struct seq_file *s, void *data) { int i = 0; size_t size = 0; struct msm_rpmh_master_stats *record = NULL; mutex_lock(&rpmh_stats_mutex); /* First Read APSS master stats */ msm_rpmh_master_stats_print_data(s, &apss_master_stats, "APSS"); /* * Read SMEM data written by masters */ for (i = 0; i < ARRAY_SIZE(rpmh_masters); i++) { record = (struct msm_rpmh_master_stats *) qcom_smem_get( rpmh_masters[i].pid, rpmh_masters[i].smem_id, &size); if (!IS_ERR_OR_NULL(record)) msm_rpmh_master_stats_print_data(s, record, rpmh_masters[i].master_name); } mutex_unlock(&rpmh_stats_mutex); return 0; } int rpmh_master_stats_open(struct inode *inode, struct file *file) { return single_open(file, rpmh_master_stats_show, inode->i_private); } static inline void msm_rpmh_apss_master_stats_update( struct msm_rpmh_profile_unit *profile_unit) { apss_master_stats.counts++; apss_master_stats.last_entered = profile_unit[POWER_DOWN_END].value; apss_master_stats.last_exited = profile_unit[POWER_UP_START].value; apss_master_stats.accumulated_duration += (apss_master_stats.last_exited - apss_master_stats.last_entered); } void msm_rpmh_master_stats_update(void) { int i; struct msm_rpmh_profile_unit profile_unit[NUM_UNIT]; if (!rpmh_unit_base) return; for (i = POWER_DOWN_END; i < NUM_UNIT; i++) { profile_unit[i].valid = readl_relaxed(rpmh_unit_base + GET_ADDR(REG_VALID, i)); /* * Do not update APSS stats if valid bit is not set. * It means APSS did not execute cx-off sequence. * This can be due to fall through at some point. */ if (!(profile_unit[i].valid & BIT(REG_VALID))) return; profile_unit[i].value = readl_relaxed(rpmh_unit_base + GET_ADDR(REG_DATA_LO, i)); profile_unit[i].value |= ((uint64_t) readl_relaxed(rpmh_unit_base + GET_ADDR(REG_DATA_HI, i)) << 32); } msm_rpmh_apss_master_stats_update(profile_unit); } EXPORT_SYMBOL(msm_rpmh_master_stats_update); ssize_t master_stats_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i = 0; size_t size = 0; struct msm_rpmh_master_stats *record = NULL; char stats_buf[1024]; uint64_t temp_accumulated_duration = 0; mutex_lock(&rpmh_stats_mutex); /* First Read APSS master stats */ temp_accumulated_duration = apss_master_stats.accumulated_duration; if (apss_master_stats.last_entered > apss_master_stats.last_exited) temp_accumulated_duration += (arch_counter_get_cntvct() - apss_master_stats.last_entered); snprintf(stats_buf, sizeof(stats_buf), "APSS\n\tVersion:0x%x\n" "\tSleep Count:0x%x\n" "\tSleep Last Entered At:0x%llx\n" "\tSleep Last Exited At:0x%llx\n" "\tSleep Accumulated Duration:0x%llx\n\n", apss_master_stats.version_id, apss_master_stats.counts, apss_master_stats.last_entered, apss_master_stats.last_exited, temp_accumulated_duration); /* * Read SMEM data written by masters */ for (i = 0; i < ARRAY_SIZE(rpmh_masters); i++) { record = (struct msm_rpmh_master_stats *) qcom_smem_get( rpmh_masters[i].pid, rpmh_masters[i].smem_id, &size); if (!IS_ERR_OR_NULL(record)) { temp_accumulated_duration = record->accumulated_duration; if (record->last_entered > record->last_exited) temp_accumulated_duration += (arch_counter_get_cntvct() - record->last_entered); snprintf(stats_buf + strlen(stats_buf), sizeof(stats_buf), "%s\n\tVersion:0x%x\n" "\tSleep Count:0x%x\n" "\tSleep Last Entered At:0x%llx\n" "\tSleep Last Exited At:0x%llx\n" "\tSleep Accumulated Duration:0x%llx\n\n", rpmh_masters[i].master_name, record->version_id, record->counts, record->last_entered, record->last_exited, temp_accumulated_duration); } } mutex_unlock(&rpmh_stats_mutex); return snprintf(buf, sizeof(stats_buf), "%s", stats_buf); } static int msm_rpmh_master_stats_probe(struct platform_device *pdev) { rpmh_unit_base = of_iomap(pdev->dev.of_node, 0); if (!rpmh_unit_base) { pr_err("Failed to get rpmh_unit_base\n"); return -ENOMEM; } apss_master_stats.version_id = 0x1; return 0; } static int msm_rpmh_master_stats_remove(struct platform_device *pdev) { iounmap(rpmh_unit_base); return 0; } static const struct of_device_id rpmh_master_table[] = { {.compatible = "qcom,rpmh-master-stats-v1"}, {}, }; static struct platform_driver msm_rpmh_master_stats_driver = { .probe = msm_rpmh_master_stats_probe, .remove = msm_rpmh_master_stats_remove, .driver = { .name = "msm_rpmh_master_stats", .of_match_table = rpmh_master_table, }, }; module_platform_driver(msm_rpmh_master_stats_driver); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MSM RPMH Master Statistics driver"); MODULE_ALIAS("platform:msm_rpmh_master_stat_log");