109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only
20e49caf6SVenkat Subbiah #include <linux/fs.h>
30e49caf6SVenkat Subbiah #include <linux/interrupt.h>
40e49caf6SVenkat Subbiah #include <asm/octeon/octeon.h>
50e49caf6SVenkat Subbiah #include <asm/octeon/cvmx-ciu-defs.h>
60e49caf6SVenkat Subbiah #include <asm/octeon/cvmx.h>
70e49caf6SVenkat Subbiah #include <linux/debugfs.h>
80e49caf6SVenkat Subbiah #include <linux/kernel.h>
90e49caf6SVenkat Subbiah #include <linux/module.h>
100e49caf6SVenkat Subbiah #include <linux/seq_file.h>
110e49caf6SVenkat Subbiah
120e49caf6SVenkat Subbiah #define TIMER_NUM 3
130e49caf6SVenkat Subbiah
140e49caf6SVenkat Subbiah static bool reset_stats;
150e49caf6SVenkat Subbiah
160e49caf6SVenkat Subbiah struct latency_info {
170e49caf6SVenkat Subbiah u64 io_interval;
180e49caf6SVenkat Subbiah u64 cpu_interval;
190e49caf6SVenkat Subbiah u64 timer_start1;
200e49caf6SVenkat Subbiah u64 timer_start2;
210e49caf6SVenkat Subbiah u64 max_latency;
220e49caf6SVenkat Subbiah u64 min_latency;
230e49caf6SVenkat Subbiah u64 latency_sum;
240e49caf6SVenkat Subbiah u64 average_latency;
250e49caf6SVenkat Subbiah u64 interrupt_cnt;
260e49caf6SVenkat Subbiah };
270e49caf6SVenkat Subbiah
280e49caf6SVenkat Subbiah static struct latency_info li;
290e49caf6SVenkat Subbiah static struct dentry *dir;
300e49caf6SVenkat Subbiah
oct_ilm_show(struct seq_file * m,void * v)31*6f674034SLiu Shixin static int oct_ilm_show(struct seq_file *m, void *v)
320e49caf6SVenkat Subbiah {
330e49caf6SVenkat Subbiah u64 cpuclk, avg, max, min;
340e49caf6SVenkat Subbiah struct latency_info curr_li = li;
350e49caf6SVenkat Subbiah
360e49caf6SVenkat Subbiah cpuclk = octeon_get_clock_rate();
370e49caf6SVenkat Subbiah
380e49caf6SVenkat Subbiah max = (curr_li.max_latency * 1000000000) / cpuclk;
390e49caf6SVenkat Subbiah min = (curr_li.min_latency * 1000000000) / cpuclk;
400e49caf6SVenkat Subbiah avg = (curr_li.latency_sum * 1000000000) / (cpuclk * curr_li.interrupt_cnt);
410e49caf6SVenkat Subbiah
420e49caf6SVenkat Subbiah seq_printf(m, "cnt: %10lld, avg: %7lld ns, max: %7lld ns, min: %7lld ns\n",
430e49caf6SVenkat Subbiah curr_li.interrupt_cnt, avg, max, min);
440e49caf6SVenkat Subbiah return 0;
450e49caf6SVenkat Subbiah }
46*6f674034SLiu Shixin DEFINE_SHOW_ATTRIBUTE(oct_ilm);
470e49caf6SVenkat Subbiah
reset_statistics(void * data,u64 value)480e49caf6SVenkat Subbiah static int reset_statistics(void *data, u64 value)
490e49caf6SVenkat Subbiah {
500e49caf6SVenkat Subbiah reset_stats = true;
510e49caf6SVenkat Subbiah return 0;
520e49caf6SVenkat Subbiah }
530e49caf6SVenkat Subbiah
541f4e5f03SYang Li DEFINE_DEBUGFS_ATTRIBUTE(reset_statistics_ops, NULL, reset_statistics, "%llu\n");
550e49caf6SVenkat Subbiah
init_debugfs(void)569afbb713SGreg Kroah-Hartman static void init_debugfs(void)
570e49caf6SVenkat Subbiah {
580e49caf6SVenkat Subbiah dir = debugfs_create_dir("oct_ilm", 0);
59*6f674034SLiu Shixin debugfs_create_file("statistics", 0222, dir, NULL, &oct_ilm_fops);
609afbb713SGreg Kroah-Hartman debugfs_create_file("reset", 0222, dir, NULL, &reset_statistics_ops);
610e49caf6SVenkat Subbiah }
620e49caf6SVenkat Subbiah
init_latency_info(struct latency_info * li,int startup)630e49caf6SVenkat Subbiah static void init_latency_info(struct latency_info *li, int startup)
640e49caf6SVenkat Subbiah {
650e49caf6SVenkat Subbiah /* interval in milli seconds after which the interrupt will
660e49caf6SVenkat Subbiah * be triggered
670e49caf6SVenkat Subbiah */
680e49caf6SVenkat Subbiah int interval = 1;
690e49caf6SVenkat Subbiah
700e49caf6SVenkat Subbiah if (startup) {
710e49caf6SVenkat Subbiah /* Calculating by the amounts io clock and cpu clock would
720e49caf6SVenkat Subbiah * increment in interval amount of ms
730e49caf6SVenkat Subbiah */
740e49caf6SVenkat Subbiah li->io_interval = (octeon_get_io_clock_rate() * interval) / 1000;
750e49caf6SVenkat Subbiah li->cpu_interval = (octeon_get_clock_rate() * interval) / 1000;
760e49caf6SVenkat Subbiah }
770e49caf6SVenkat Subbiah li->timer_start1 = 0;
780e49caf6SVenkat Subbiah li->timer_start2 = 0;
790e49caf6SVenkat Subbiah li->max_latency = 0;
800e49caf6SVenkat Subbiah li->min_latency = (u64)-1;
810e49caf6SVenkat Subbiah li->latency_sum = 0;
820e49caf6SVenkat Subbiah li->interrupt_cnt = 0;
830e49caf6SVenkat Subbiah }
840e49caf6SVenkat Subbiah
850e49caf6SVenkat Subbiah
start_timer(int timer,u64 interval)860e49caf6SVenkat Subbiah static void start_timer(int timer, u64 interval)
870e49caf6SVenkat Subbiah {
880e49caf6SVenkat Subbiah union cvmx_ciu_timx timx;
890e49caf6SVenkat Subbiah unsigned long flags;
900e49caf6SVenkat Subbiah
910e49caf6SVenkat Subbiah timx.u64 = 0;
920e49caf6SVenkat Subbiah timx.s.one_shot = 1;
930e49caf6SVenkat Subbiah timx.s.len = interval;
940e49caf6SVenkat Subbiah raw_local_irq_save(flags);
950e49caf6SVenkat Subbiah li.timer_start1 = read_c0_cvmcount();
960e49caf6SVenkat Subbiah cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64);
970e49caf6SVenkat Subbiah /* Read it back to force wait until register is written. */
980e49caf6SVenkat Subbiah timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer));
990e49caf6SVenkat Subbiah li.timer_start2 = read_c0_cvmcount();
1000e49caf6SVenkat Subbiah raw_local_irq_restore(flags);
1010e49caf6SVenkat Subbiah }
1020e49caf6SVenkat Subbiah
1030e49caf6SVenkat Subbiah
cvm_oct_ciu_timer_interrupt(int cpl,void * dev_id)1040e49caf6SVenkat Subbiah static irqreturn_t cvm_oct_ciu_timer_interrupt(int cpl, void *dev_id)
1050e49caf6SVenkat Subbiah {
1060e49caf6SVenkat Subbiah u64 last_latency;
1070e49caf6SVenkat Subbiah u64 last_int_cnt;
1080e49caf6SVenkat Subbiah
1090e49caf6SVenkat Subbiah if (reset_stats) {
1100e49caf6SVenkat Subbiah init_latency_info(&li, 0);
1110e49caf6SVenkat Subbiah reset_stats = false;
1120e49caf6SVenkat Subbiah } else {
1130e49caf6SVenkat Subbiah last_int_cnt = read_c0_cvmcount();
1140e49caf6SVenkat Subbiah last_latency = last_int_cnt - (li.timer_start1 + li.cpu_interval);
1150e49caf6SVenkat Subbiah li.interrupt_cnt++;
1160e49caf6SVenkat Subbiah li.latency_sum += last_latency;
1170e49caf6SVenkat Subbiah if (last_latency > li.max_latency)
1180e49caf6SVenkat Subbiah li.max_latency = last_latency;
1190e49caf6SVenkat Subbiah if (last_latency < li.min_latency)
1200e49caf6SVenkat Subbiah li.min_latency = last_latency;
1210e49caf6SVenkat Subbiah }
1220e49caf6SVenkat Subbiah start_timer(TIMER_NUM, li.io_interval);
1230e49caf6SVenkat Subbiah return IRQ_HANDLED;
1240e49caf6SVenkat Subbiah }
1250e49caf6SVenkat Subbiah
disable_timer(int timer)1260e49caf6SVenkat Subbiah static void disable_timer(int timer)
1270e49caf6SVenkat Subbiah {
1280e49caf6SVenkat Subbiah union cvmx_ciu_timx timx;
1290e49caf6SVenkat Subbiah
1300e49caf6SVenkat Subbiah timx.s.one_shot = 0;
1310e49caf6SVenkat Subbiah timx.s.len = 0;
1320e49caf6SVenkat Subbiah cvmx_write_csr(CVMX_CIU_TIMX(timer), timx.u64);
1330e49caf6SVenkat Subbiah /* Read it back to force immediate write of timer register*/
1340e49caf6SVenkat Subbiah timx.u64 = cvmx_read_csr(CVMX_CIU_TIMX(timer));
1350e49caf6SVenkat Subbiah }
1360e49caf6SVenkat Subbiah
oct_ilm_module_init(void)1370e49caf6SVenkat Subbiah static __init int oct_ilm_module_init(void)
1380e49caf6SVenkat Subbiah {
1390e49caf6SVenkat Subbiah int rc;
1400e49caf6SVenkat Subbiah int irq = OCTEON_IRQ_TIMER0 + TIMER_NUM;
1410e49caf6SVenkat Subbiah
1429afbb713SGreg Kroah-Hartman init_debugfs();
1430e49caf6SVenkat Subbiah
1440e49caf6SVenkat Subbiah rc = request_irq(irq, cvm_oct_ciu_timer_interrupt, IRQF_NO_THREAD,
1450e49caf6SVenkat Subbiah "oct_ilm", 0);
1460e49caf6SVenkat Subbiah if (rc) {
1470e49caf6SVenkat Subbiah WARN(1, "Could not acquire IRQ %d", irq);
1480e49caf6SVenkat Subbiah goto err_irq;
1490e49caf6SVenkat Subbiah }
1500e49caf6SVenkat Subbiah
1510e49caf6SVenkat Subbiah init_latency_info(&li, 1);
1520e49caf6SVenkat Subbiah start_timer(TIMER_NUM, li.io_interval);
1530e49caf6SVenkat Subbiah
1540e49caf6SVenkat Subbiah return 0;
1550e49caf6SVenkat Subbiah err_irq:
1560e49caf6SVenkat Subbiah debugfs_remove_recursive(dir);
1570e49caf6SVenkat Subbiah return rc;
1580e49caf6SVenkat Subbiah }
1590e49caf6SVenkat Subbiah
oct_ilm_module_exit(void)1600e49caf6SVenkat Subbiah static __exit void oct_ilm_module_exit(void)
1610e49caf6SVenkat Subbiah {
1620e49caf6SVenkat Subbiah disable_timer(TIMER_NUM);
1630e49caf6SVenkat Subbiah debugfs_remove_recursive(dir);
1640e49caf6SVenkat Subbiah free_irq(OCTEON_IRQ_TIMER0 + TIMER_NUM, 0);
1650e49caf6SVenkat Subbiah }
1660e49caf6SVenkat Subbiah
1670e49caf6SVenkat Subbiah module_exit(oct_ilm_module_exit);
1680e49caf6SVenkat Subbiah module_init(oct_ilm_module_init);
1690e49caf6SVenkat Subbiah MODULE_AUTHOR("Venkat Subbiah, Cavium");
1700e49caf6SVenkat Subbiah MODULE_DESCRIPTION("Measures interrupt latency on Octeon chips.");
1710e49caf6SVenkat Subbiah MODULE_LICENSE("GPL");
172