162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
462306a36Sopenharmony_ci * Copyright (c) 2023, Linaro Limited
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * This driver supports what is known as "Master Stats v2" in Qualcomm
762306a36Sopenharmony_ci * downstream kernel terms, which seems to be the only version which has
862306a36Sopenharmony_ci * ever shipped, all the way from 2013 to 2023.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/debugfs.h>
1262306a36Sopenharmony_ci#include <linux/io.h>
1362306a36Sopenharmony_ci#include <linux/module.h>
1462306a36Sopenharmony_ci#include <linux/of.h>
1562306a36Sopenharmony_ci#include <linux/of_address.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistruct master_stats_data {
1962306a36Sopenharmony_ci	void __iomem *base;
2062306a36Sopenharmony_ci	const char *label;
2162306a36Sopenharmony_ci};
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_cistruct rpm_master_stats {
2462306a36Sopenharmony_ci	u32 active_cores;
2562306a36Sopenharmony_ci	u32 num_shutdowns;
2662306a36Sopenharmony_ci	u64 shutdown_req;
2762306a36Sopenharmony_ci	u64 wakeup_idx;
2862306a36Sopenharmony_ci	u64 bringup_req;
2962306a36Sopenharmony_ci	u64 bringup_ack;
3062306a36Sopenharmony_ci	u32 wakeup_reason; /* 0 = "rude wakeup", 1 = scheduled wakeup */
3162306a36Sopenharmony_ci	u32 last_sleep_trans_dur;
3262306a36Sopenharmony_ci	u32 last_wake_trans_dur;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	/* Per-subsystem (*not necessarily* SoC-wide) XO shutdown stats */
3562306a36Sopenharmony_ci	u32 xo_count;
3662306a36Sopenharmony_ci	u64 xo_last_enter;
3762306a36Sopenharmony_ci	u64 last_exit;
3862306a36Sopenharmony_ci	u64 xo_total_dur;
3962306a36Sopenharmony_ci} __packed;
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_cistatic int master_stats_show(struct seq_file *s, void *unused)
4262306a36Sopenharmony_ci{
4362306a36Sopenharmony_ci	struct master_stats_data *data = s->private;
4462306a36Sopenharmony_ci	struct rpm_master_stats stat;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	memcpy_fromio(&stat, data->base, sizeof(stat));
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	seq_printf(s, "%s:\n", data->label);
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci	seq_printf(s, "\tLast shutdown @ %llu\n", stat.shutdown_req);
5162306a36Sopenharmony_ci	seq_printf(s, "\tLast bringup req @ %llu\n", stat.bringup_req);
5262306a36Sopenharmony_ci	seq_printf(s, "\tLast bringup ack @ %llu\n", stat.bringup_ack);
5362306a36Sopenharmony_ci	seq_printf(s, "\tLast wakeup idx: %llu\n", stat.wakeup_idx);
5462306a36Sopenharmony_ci	seq_printf(s, "\tLast XO shutdown enter @ %llu\n", stat.xo_last_enter);
5562306a36Sopenharmony_ci	seq_printf(s, "\tLast XO shutdown exit @ %llu\n", stat.last_exit);
5662306a36Sopenharmony_ci	seq_printf(s, "\tXO total duration: %llu\n", stat.xo_total_dur);
5762306a36Sopenharmony_ci	seq_printf(s, "\tLast sleep transition duration: %u\n", stat.last_sleep_trans_dur);
5862306a36Sopenharmony_ci	seq_printf(s, "\tLast wake transition duration: %u\n", stat.last_wake_trans_dur);
5962306a36Sopenharmony_ci	seq_printf(s, "\tXO shutdown count: %u\n", stat.xo_count);
6062306a36Sopenharmony_ci	seq_printf(s, "\tWakeup reason: 0x%x\n", stat.wakeup_reason);
6162306a36Sopenharmony_ci	seq_printf(s, "\tShutdown count: %u\n", stat.num_shutdowns);
6262306a36Sopenharmony_ci	seq_printf(s, "\tActive cores bitmask: 0x%x\n", stat.active_cores);
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return 0;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ciDEFINE_SHOW_ATTRIBUTE(master_stats);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int master_stats_probe(struct platform_device *pdev)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
7162306a36Sopenharmony_ci	struct master_stats_data *data;
7262306a36Sopenharmony_ci	struct device_node *msgram_np;
7362306a36Sopenharmony_ci	struct dentry *dent, *root;
7462306a36Sopenharmony_ci	struct resource res;
7562306a36Sopenharmony_ci	int count, i, ret;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	count = of_property_count_strings(dev->of_node, "qcom,master-names");
7862306a36Sopenharmony_ci	if (count < 0)
7962306a36Sopenharmony_ci		return count;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	data = devm_kzalloc(dev, count * sizeof(*data), GFP_KERNEL);
8262306a36Sopenharmony_ci	if (!data)
8362306a36Sopenharmony_ci		return -ENOMEM;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	root = debugfs_create_dir("qcom_rpm_master_stats", NULL);
8662306a36Sopenharmony_ci	platform_set_drvdata(pdev, root);
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	for (i = 0; i < count; i++) {
8962306a36Sopenharmony_ci		msgram_np = of_parse_phandle(dev->of_node, "qcom,rpm-msg-ram", i);
9062306a36Sopenharmony_ci		if (!msgram_np) {
9162306a36Sopenharmony_ci			debugfs_remove_recursive(root);
9262306a36Sopenharmony_ci			return dev_err_probe(dev, -ENODEV,
9362306a36Sopenharmony_ci					     "Couldn't parse MSG RAM phandle idx %d", i);
9462306a36Sopenharmony_ci		}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci		/*
9762306a36Sopenharmony_ci		 * Purposefully skip devm_platform helpers as we're using a
9862306a36Sopenharmony_ci		 * shared resource.
9962306a36Sopenharmony_ci		 */
10062306a36Sopenharmony_ci		ret = of_address_to_resource(msgram_np, 0, &res);
10162306a36Sopenharmony_ci		of_node_put(msgram_np);
10262306a36Sopenharmony_ci		if (ret < 0) {
10362306a36Sopenharmony_ci			debugfs_remove_recursive(root);
10462306a36Sopenharmony_ci			return ret;
10562306a36Sopenharmony_ci		}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci		data[i].base = devm_ioremap(dev, res.start, resource_size(&res));
10862306a36Sopenharmony_ci		if (!data[i].base) {
10962306a36Sopenharmony_ci			debugfs_remove_recursive(root);
11062306a36Sopenharmony_ci			return dev_err_probe(dev, -EINVAL,
11162306a36Sopenharmony_ci					     "Could not map the MSG RAM slice idx %d!\n", i);
11262306a36Sopenharmony_ci		}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci		ret = of_property_read_string_index(dev->of_node, "qcom,master-names", i,
11562306a36Sopenharmony_ci						    &data[i].label);
11662306a36Sopenharmony_ci		if (ret < 0) {
11762306a36Sopenharmony_ci			debugfs_remove_recursive(root);
11862306a36Sopenharmony_ci			return dev_err_probe(dev, ret,
11962306a36Sopenharmony_ci					     "Could not read name idx %d!\n", i);
12062306a36Sopenharmony_ci		}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		/*
12362306a36Sopenharmony_ci		 * Generally it's not advised to fail on debugfs errors, but this
12462306a36Sopenharmony_ci		 * driver's only job is exposing data therein.
12562306a36Sopenharmony_ci		 */
12662306a36Sopenharmony_ci		dent = debugfs_create_file(data[i].label, 0444, root,
12762306a36Sopenharmony_ci					   &data[i], &master_stats_fops);
12862306a36Sopenharmony_ci		if (IS_ERR(dent)) {
12962306a36Sopenharmony_ci			debugfs_remove_recursive(root);
13062306a36Sopenharmony_ci			return dev_err_probe(dev, PTR_ERR(dent),
13162306a36Sopenharmony_ci					     "Failed to create debugfs file %s!\n", data[i].label);
13262306a36Sopenharmony_ci		}
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	device_set_pm_not_required(dev);
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return 0;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_cistatic void master_stats_remove(struct platform_device *pdev)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	struct dentry *root = platform_get_drvdata(pdev);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	debugfs_remove_recursive(root);
14562306a36Sopenharmony_ci}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_cistatic const struct of_device_id rpm_master_table[] = {
14862306a36Sopenharmony_ci	{ .compatible = "qcom,rpm-master-stats" },
14962306a36Sopenharmony_ci	{ },
15062306a36Sopenharmony_ci};
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_cistatic struct platform_driver master_stats_driver = {
15362306a36Sopenharmony_ci	.probe = master_stats_probe,
15462306a36Sopenharmony_ci	.remove_new = master_stats_remove,
15562306a36Sopenharmony_ci	.driver = {
15662306a36Sopenharmony_ci		.name = "qcom_rpm_master_stats",
15762306a36Sopenharmony_ci		.of_match_table = rpm_master_table,
15862306a36Sopenharmony_ci	},
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_cimodule_platform_driver(master_stats_driver);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ciMODULE_DESCRIPTION("Qualcomm RPM Master Statistics driver");
16362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
164