/* Copyright (c) 2012-2014, 2016-2017, 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rpm_stats.h" #define RPM_MASTERS_BUF_LEN 400 #define SNPRINTF(buf, size, format, ...) \ do { \ if (size > 0) { \ int ret; \ ret = snprintf(buf, size, format, ## __VA_ARGS__); \ if (ret > size) { \ buf += size; \ size = 0; \ } else { \ buf += ret; \ size -= ret; \ } \ } \ } while (0) #define GET_MASTER_NAME(a, prvdata) \ ((a >= prvdata->num_masters) ? "Invalid Master Name" : \ prvdata->master_names[a]) #define GET_FIELD(a) ((strnstr(#a, ".", 80) + 1)) static DEFINE_MUTEX(msm_rpm_master_stats_mutex); struct msm_rpm_master_stats { uint32_t active_cores; uint32_t numshutdowns; uint64_t shutdown_req; uint64_t wakeup_ind; uint64_t bringup_req; uint64_t bringup_ack; uint32_t wakeup_reason; /* 0 = rude wakeup, 1 = scheduled wakeup */ uint32_t last_sleep_transition_duration; uint32_t last_wake_transition_duration; }; struct msm_rpm_master_stats_private_data { void __iomem *reg_base; u32 len; char **master_names; u32 num_masters; char buf[RPM_MASTERS_BUF_LEN]; struct msm_rpm_master_stats_platform_data *platform_data; }; int msm_rpm_master_stats_file_close(struct inode *inode, struct file *file) { struct msm_rpm_master_stats_private_data *private = file->private_data; mutex_lock(&msm_rpm_master_stats_mutex); if (private->reg_base) iounmap(private->reg_base); kfree(file->private_data); mutex_unlock(&msm_rpm_master_stats_mutex); return 0; } static int msm_rpm_master_copy_stats( struct msm_rpm_master_stats_private_data *prvdata) { struct msm_rpm_master_stats record; struct msm_rpm_master_stats_platform_data *pdata; static int master_cnt; int count, j = 0; char *buf; /* Iterate possible number of masters */ if (master_cnt > prvdata->num_masters - 1) { master_cnt = 0; return 0; } pdata = prvdata->platform_data; count = RPM_MASTERS_BUF_LEN; buf = prvdata->buf; if (prvdata->platform_data->version == 2) { SNPRINTF(buf, count, "%s\n", GET_MASTER_NAME(master_cnt, prvdata)); record.shutdown_req = readq_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, shutdown_req))); SNPRINTF(buf, count, "\t%s:0x%llX\n", GET_FIELD(record.shutdown_req), record.shutdown_req); record.wakeup_ind = readq_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, wakeup_ind))); SNPRINTF(buf, count, "\t%s:0x%llX\n", GET_FIELD(record.wakeup_ind), record.wakeup_ind); record.bringup_req = readq_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, bringup_req))); SNPRINTF(buf, count, "\t%s:0x%llX\n", GET_FIELD(record.bringup_req), record.bringup_req); record.bringup_ack = readq_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, bringup_ack))); SNPRINTF(buf, count, "\t%s:0x%llX\n", GET_FIELD(record.bringup_ack), record.bringup_ack); record.last_sleep_transition_duration = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, last_sleep_transition_duration))); SNPRINTF(buf, count, "\t%s:0x%x\n", GET_FIELD(record.last_sleep_transition_duration), record.last_sleep_transition_duration); record.last_wake_transition_duration = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, last_wake_transition_duration))); SNPRINTF(buf, count, "\t%s:0x%x\n", GET_FIELD(record.last_wake_transition_duration), record.last_wake_transition_duration); record.wakeup_reason = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, wakeup_reason))); SNPRINTF(buf, count, "\t%s:0x%x\n", GET_FIELD(record.wakeup_reason), record.wakeup_reason); record.numshutdowns = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset + offsetof(struct msm_rpm_master_stats, numshutdowns))); SNPRINTF(buf, count, "\t%s:0x%x\n", GET_FIELD(record.numshutdowns), record.numshutdowns); record.active_cores = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset) + offsetof(struct msm_rpm_master_stats, active_cores)); SNPRINTF(buf, count, "\t%s:0x%x\n", GET_FIELD(record.active_cores), record.active_cores); } else { SNPRINTF(buf, count, "%s\n", GET_MASTER_NAME(master_cnt, prvdata)); record.numshutdowns = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset) + 0x0); SNPRINTF(buf, count, "\t%s:0x%0x\n", GET_FIELD(record.numshutdowns), record.numshutdowns); record.active_cores = readl_relaxed(prvdata->reg_base + (master_cnt * pdata->master_offset) + 0x4); SNPRINTF(buf, count, "\t%s:0x%0x\n", GET_FIELD(record.active_cores), record.active_cores); } j = find_first_bit((unsigned long *)&record.active_cores, BITS_PER_LONG); while (j < BITS_PER_LONG) { SNPRINTF(buf, count, "\t\tcore%d\n", j); j = find_next_bit((unsigned long *)&record.active_cores, BITS_PER_LONG, j + 1); } master_cnt++; return RPM_MASTERS_BUF_LEN - count; } static ssize_t msm_rpm_master_stats_file_read(struct file *file, char __user *bufu, size_t count, loff_t *ppos) { struct msm_rpm_master_stats_private_data *prvdata; struct msm_rpm_master_stats_platform_data *pdata; ssize_t ret; mutex_lock(&msm_rpm_master_stats_mutex); prvdata = file->private_data; if (!prvdata) { ret = -EINVAL; goto exit; } pdata = prvdata->platform_data; if (!pdata) { ret = -EINVAL; goto exit; } if (!bufu || count == 0) { ret = -EINVAL; goto exit; } if ((*ppos <= pdata->phys_size)) { prvdata->len = msm_rpm_master_copy_stats(prvdata); *ppos = 0; } ret = simple_read_from_buffer(bufu, count, ppos, prvdata->buf, prvdata->len); exit: mutex_unlock(&msm_rpm_master_stats_mutex); return ret; } static int msm_rpm_master_stats_file_open(struct inode *inode, struct file *file) { struct msm_rpm_master_stats_private_data *prvdata; struct msm_rpm_master_stats_platform_data *pdata; int ret = 0; mutex_lock(&msm_rpm_master_stats_mutex); pdata = inode->i_private; file->private_data = kzalloc(sizeof(struct msm_rpm_master_stats_private_data), GFP_KERNEL); if (!file->private_data) { ret = -ENOMEM; goto exit; } prvdata = file->private_data; prvdata->reg_base = ioremap(pdata->phys_addr_base, pdata->phys_size); if (!prvdata->reg_base) { kfree(file->private_data); prvdata = NULL; pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n", __func__, &pdata->phys_addr_base, pdata->phys_size); ret = -EBUSY; goto exit; } prvdata->len = 0; prvdata->num_masters = pdata->num_masters; prvdata->master_names = pdata->masters; prvdata->platform_data = pdata; exit: mutex_unlock(&msm_rpm_master_stats_mutex); return ret; } static const struct file_operations msm_rpm_master_stats_fops = { .owner = THIS_MODULE, .open = msm_rpm_master_stats_file_open, .read = msm_rpm_master_stats_file_read, .release = msm_rpm_master_stats_file_close, .llseek = no_llseek, }; static struct msm_rpm_master_stats_platform_data *msm_rpm_master_populate_pdata(struct device *dev) { struct msm_rpm_master_stats_platform_data *pdata; struct device_node *node = dev->of_node; int rc = 0, i; pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { dev_err(dev, "could not allocate memory for platform data\n"); goto err; } rc = of_property_read_u32(node, "qcom,master-stats-version", &pdata->version); if (rc) { dev_err(dev, "master-stats-version missing rc=%d\n", rc); goto err; } rc = of_property_read_u32(node, "qcom,master-offset", &pdata->master_offset); if (rc) { dev_err(dev, "master-offset missing rc=%d\n", rc); goto err; } pdata->num_masters = of_property_count_strings(node, "qcom,masters"); if (pdata->num_masters < 0) { dev_err(dev, "Failed to get number of masters =%d\n", pdata->num_masters); goto err; } pdata->masters = devm_kzalloc(dev, sizeof(char *) * pdata->num_masters, GFP_KERNEL); if (!pdata->masters) { dev_err(dev, "%s:Failed to allocated memory\n", __func__); goto err; } /* * Read master names from DT */ for (i = 0; i < pdata->num_masters; i++) { const char *master_name; of_property_read_string_index(node, "qcom,masters", i, &master_name); pdata->masters[i] = devm_kzalloc(dev, sizeof(char) * strlen(master_name) + 1, GFP_KERNEL); if (!pdata->masters[i]) { dev_err(dev, "%s:Failed to get memory\n", __func__); goto err; } strlcpy(pdata->masters[i], master_name, strlen(master_name) + 1); } return pdata; err: return NULL; } static int msm_rpm_master_stats_probe(struct platform_device *pdev) { struct dentry *dent; struct msm_rpm_master_stats_platform_data *pdata; struct resource *res = NULL; if (!pdev) return -EINVAL; if (pdev->dev.of_node) pdata = msm_rpm_master_populate_pdata(&pdev->dev); else pdata = pdev->dev.platform_data; if (!pdata) { dev_err(&pdev->dev, "%s: Unable to get pdata\n", __func__); return -ENOMEM; } res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "%s: Failed to get IO resource from platform device", __func__); return -ENXIO; } pdata->phys_addr_base = res->start; pdata->phys_size = resource_size(res); dent = debugfs_create_file("rpm_master_stats", S_IRUGO, NULL, pdata, &msm_rpm_master_stats_fops); if (!dent) { dev_err(&pdev->dev, "%s: ERROR debugfs_create_file failed\n", __func__); return -ENOMEM; } platform_set_drvdata(pdev, dent); return 0; } static int msm_rpm_master_stats_remove(struct platform_device *pdev) { struct dentry *dent; dent = platform_get_drvdata(pdev); debugfs_remove(dent); platform_set_drvdata(pdev, NULL); return 0; } static struct of_device_id rpm_master_table[] = { {.compatible = "qcom,rpm-master-stats"}, {}, }; static struct platform_driver msm_rpm_master_stats_driver = { .probe = msm_rpm_master_stats_probe, .remove = msm_rpm_master_stats_remove, .driver = { .name = "msm_rpm_master_stats", .owner = THIS_MODULE, .of_match_table = rpm_master_table, }, }; static int __init msm_rpm_master_stats_init(void) { return platform_driver_register(&msm_rpm_master_stats_driver); } static void __exit msm_rpm_master_stats_exit(void) { platform_driver_unregister(&msm_rpm_master_stats_driver); } module_init(msm_rpm_master_stats_init); module_exit(msm_rpm_master_stats_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MSM RPM Master Statistics driver"); MODULE_VERSION("1.0"); MODULE_ALIAS("platform:msm_master_stat_log");