18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci#include <linux/fs.h>
38c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
48c2ecf20Sopenharmony_ci#include <asm/octeon/octeon.h>
58c2ecf20Sopenharmony_ci#include <asm/octeon/cvmx-ciu-defs.h>
68c2ecf20Sopenharmony_ci#include <asm/octeon/cvmx.h>
78c2ecf20Sopenharmony_ci#include <linux/debugfs.h>
88c2ecf20Sopenharmony_ci#include <linux/kernel.h>
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci#define TIMER_NUM 3
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic bool reset_stats;
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_cistruct latency_info {
178c2ecf20Sopenharmony_ci	u64 io_interval;
188c2ecf20Sopenharmony_ci	u64 cpu_interval;
198c2ecf20Sopenharmony_ci	u64 timer_start1;
208c2ecf20Sopenharmony_ci	u64 timer_start2;
218c2ecf20Sopenharmony_ci	u64 max_latency;
228c2ecf20Sopenharmony_ci	u64 min_latency;
238c2ecf20Sopenharmony_ci	u64 latency_sum;
248c2ecf20Sopenharmony_ci	u64 average_latency;
258c2ecf20Sopenharmony_ci	u64 interrupt_cnt;
268c2ecf20Sopenharmony_ci};
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic struct latency_info li;
298c2ecf20Sopenharmony_cistatic struct dentry *dir;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic int show_latency(struct seq_file *m, void *v)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	u64 cpuclk, avg, max, min;
348c2ecf20Sopenharmony_ci	struct latency_info curr_li = li;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	cpuclk = octeon_get_clock_rate();
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci	max = (curr_li.max_latency * 1000000000) / cpuclk;
398c2ecf20Sopenharmony_ci	min = (curr_li.min_latency * 1000000000) / cpuclk;
408c2ecf20Sopenharmony_ci	avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	seq_printf(m, "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n",
438c2ecf20Sopenharmony_ci		   curr_li.interrupt_cnt, avg, max, min);
448c2ecf20Sopenharmony_ci	return 0;
458c2ecf20Sopenharmony_ci}
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_cistatic int oct_ilm_open(struct inode *inode, struct file *file)
488c2ecf20Sopenharmony_ci{
498c2ecf20Sopenharmony_ci	return single_open(file, show_latency, NULL);
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic const struct file_operations oct_ilm_ops = {
538c2ecf20Sopenharmony_ci	.open = oct_ilm_open,
548c2ecf20Sopenharmony_ci	.read = seq_read,
558c2ecf20Sopenharmony_ci	.llseek = seq_lseek,
568c2ecf20Sopenharmony_ci	.release = single_release,
578c2ecf20Sopenharmony_ci};
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_cistatic int reset_statistics(void *data, u64 value)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	reset_stats = true;
628c2ecf20Sopenharmony_ci	return 0;
638c2ecf20Sopenharmony_ci}
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ciDEFINE_SIMPLE_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n");
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic void init_debugfs(void)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	dir = debugfs_create_dir("oct_ilm", 0);
708c2ecf20Sopenharmony_ci	debugfs_create_file("statistics", 0222, dir, NULL, &oct_ilm_ops);
718c2ecf20Sopenharmony_ci	debugfs_create_file("reset", 0222, dir, NULL, &reset_statistics_ops);
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic void init_latency_info(struct latency_info *li, int startup)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	/* interval in milli seconds after which the interrupt will
778c2ecf20Sopenharmony_ci	 * be triggered
788c2ecf20Sopenharmony_ci	 */
798c2ecf20Sopenharmony_ci	int interval = 1;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (startup) {
828c2ecf20Sopenharmony_ci		/* Calculating by the amounts io clock and cpu clock would
838c2ecf20Sopenharmony_ci		 *  increment in interval amount of ms
848c2ecf20Sopenharmony_ci		 */
858c2ecf20Sopenharmony_ci		li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000;
868c2ecf20Sopenharmony_ci		li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000;
878c2ecf20Sopenharmony_ci	}
888c2ecf20Sopenharmony_ci	li->timer_start1 = 0;
898c2ecf20Sopenharmony_ci	li->timer_start2 = 0;
908c2ecf20Sopenharmony_ci	li->max_latency = 0;
918c2ecf20Sopenharmony_ci	li->min_latency = (u64)-1;
928c2ecf20Sopenharmony_ci	li->latency_sum = 0;
938c2ecf20Sopenharmony_ci	li->interrupt_cnt = 0;
948c2ecf20Sopenharmony_ci}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_cistatic void start_timer(int timer, u64 interval)
988c2ecf20Sopenharmony_ci{
998c2ecf20Sopenharmony_ci	union cvmx_ciu_timx timx;
1008c2ecf20Sopenharmony_ci	unsigned long flags;
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci	timx.u64 = 0;
1038c2ecf20Sopenharmony_ci	timx.s.one_shot = 1;
1048c2ecf20Sopenharmony_ci	timx.s.len = interval;
1058c2ecf20Sopenharmony_ci	raw_local_irq_save(flags);
1068c2ecf20Sopenharmony_ci	li.timer_start1 = read_c0_cvmcount();
1078c2ecf20Sopenharmony_ci	cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64);
1088c2ecf20Sopenharmony_ci	/* Read it back to force wait until register is written. */
1098c2ecf20Sopenharmony_ci	timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer));
1108c2ecf20Sopenharmony_ci	li.timer_start2 = read_c0_cvmcount();
1118c2ecf20Sopenharmony_ci	raw_local_irq_restore(flags);
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_cistatic irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id)
1168c2ecf20Sopenharmony_ci{
1178c2ecf20Sopenharmony_ci	u64 last_latency;
1188c2ecf20Sopenharmony_ci	u64 last_int_cnt;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	if (reset_stats) {
1218c2ecf20Sopenharmony_ci		init_latency_info(&li, 0);
1228c2ecf20Sopenharmony_ci		reset_stats = false;
1238c2ecf20Sopenharmony_ci	} else {
1248c2ecf20Sopenharmony_ci		last_int_cnt = read_c0_cvmcount();
1258c2ecf20Sopenharmony_ci		last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval);
1268c2ecf20Sopenharmony_ci		li.interrupt_cnt++;
1278c2ecf20Sopenharmony_ci		li.latency_sum += last_latency;
1288c2ecf20Sopenharmony_ci		if (last_latency > li.max_latency)
1298c2ecf20Sopenharmony_ci			li.max_latency = last_latency;
1308c2ecf20Sopenharmony_ci		if (last_latency < li.min_latency)
1318c2ecf20Sopenharmony_ci			li.min_latency = last_latency;
1328c2ecf20Sopenharmony_ci	}
1338c2ecf20Sopenharmony_ci	start_timer(TIMER_NUM, li.io_interval);
1348c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1358c2ecf20Sopenharmony_ci}
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_cistatic void disable_timer(int timer)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	union cvmx_ciu_timx timx;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	timx.s.one_shot = 0;
1428c2ecf20Sopenharmony_ci	timx.s.len = 0;
1438c2ecf20Sopenharmony_ci	cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64);
1448c2ecf20Sopenharmony_ci	/* Read it back to force immediate write of timer register*/
1458c2ecf20Sopenharmony_ci	timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer));
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic __init int oct_ilm_module_init(void)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	int rc;
1518c2ecf20Sopenharmony_ci	int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM;
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	init_debugfs();
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	rc = request_irq(irq, cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD,
1568c2ecf20Sopenharmony_ci			 "oct_ilm", 0);
1578c2ecf20Sopenharmony_ci	if (rc) {
1588c2ecf20Sopenharmony_ci		WARN(1, "Could not acquire IRQ %d", irq);
1598c2ecf20Sopenharmony_ci		goto err_irq;
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	init_latency_info(&li, 1);
1638c2ecf20Sopenharmony_ci	start_timer(TIMER_NUM, li.io_interval);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	return 0;
1668c2ecf20Sopenharmony_cierr_irq:
1678c2ecf20Sopenharmony_ci	debugfs_remove_recursive(dir);
1688c2ecf20Sopenharmony_ci	return rc;
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic __exit void oct_ilm_module_exit(void)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	disable_timer(TIMER_NUM);
1748c2ecf20Sopenharmony_ci	debugfs_remove_recursive(dir);
1758c2ecf20Sopenharmony_ci	free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0);
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cimodule_exit(oct_ilm_module_exit);
1798c2ecf20Sopenharmony_cimodule_init(oct_ilm_module_init);
1808c2ecf20Sopenharmony_ciMODULE_AUTHOR("Venkat Subbiah, Cavium");
1818c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Measures interrupt latency on Octeon chips.");
1828c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
183