18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
38c2ecf20Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
48c2ecf20Sopenharmony_ci * for more details.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci#include <linux/init.h>
88c2ecf20Sopenharmony_ci#include <linux/cpu.h>
98c2ecf20Sopenharmony_ci#include <linux/smp.h>
108c2ecf20Sopenharmony_ci#include <linux/proc_fs.h>
118c2ecf20Sopenharmony_ci#include <linux/oprofile.h>
128c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
138c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
148c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
158c2ecf20Sopenharmony_ci#include <irq.h>
168c2ecf20Sopenharmony_ci#include <loongson.h>
178c2ecf20Sopenharmony_ci#include "op_impl.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCNT_OVERFLOW	(1ULL << 63)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_EXL		(1UL << 0)
228c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_KERNEL	(1UL << 1)
238c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_SUPERVISOR	(1UL << 2)
248c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_USER		(1UL << 3)
258c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_ENABLE	(1UL << 4)
268c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_W		(1UL << 30)
278c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_M		(1UL << 31)
288c2ecf20Sopenharmony_ci#define LOONGSON3_PERFCTRL_EVENT(idx, event) \
298c2ecf20Sopenharmony_ci	(((event) & (idx ? 0x0f : 0x3f)) << 5)
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci/* Loongson-3 PerfCount performance counter1 register */
328c2ecf20Sopenharmony_ci#define read_c0_perflo1() __read_64bit_c0_register($25, 0)
338c2ecf20Sopenharmony_ci#define write_c0_perflo1(val) __write_64bit_c0_register($25, 0, val)
348c2ecf20Sopenharmony_ci#define read_c0_perfhi1() __read_64bit_c0_register($25, 1)
358c2ecf20Sopenharmony_ci#define write_c0_perfhi1(val) __write_64bit_c0_register($25, 1, val)
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci/* Loongson-3 PerfCount performance counter2 register */
388c2ecf20Sopenharmony_ci#define read_c0_perflo2() __read_64bit_c0_register($25, 2)
398c2ecf20Sopenharmony_ci#define write_c0_perflo2(val) __write_64bit_c0_register($25, 2, val)
408c2ecf20Sopenharmony_ci#define read_c0_perfhi2() __read_64bit_c0_register($25, 3)
418c2ecf20Sopenharmony_ci#define write_c0_perfhi2(val) __write_64bit_c0_register($25, 3, val)
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic int (*save_perf_irq)(void);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic struct loongson3_register_config {
468c2ecf20Sopenharmony_ci	unsigned int control1;
478c2ecf20Sopenharmony_ci	unsigned int control2;
488c2ecf20Sopenharmony_ci	unsigned long long reset_counter1;
498c2ecf20Sopenharmony_ci	unsigned long long reset_counter2;
508c2ecf20Sopenharmony_ci	int ctr1_enable, ctr2_enable;
518c2ecf20Sopenharmony_ci} reg;
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_cistatic void reset_counters(void *arg)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	write_c0_perfhi1(0);
568c2ecf20Sopenharmony_ci	write_c0_perfhi2(0);
578c2ecf20Sopenharmony_ci	write_c0_perflo1(0xc0000000);
588c2ecf20Sopenharmony_ci	write_c0_perflo2(0x40000000);
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci/* Compute all of the registers in preparation for enabling profiling. */
628c2ecf20Sopenharmony_cistatic void loongson3_reg_setup(struct op_counter_config *ctr)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	unsigned int control1 = 0;
658c2ecf20Sopenharmony_ci	unsigned int control2 = 0;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	reg.reset_counter1 = 0;
688c2ecf20Sopenharmony_ci	reg.reset_counter2 = 0;
698c2ecf20Sopenharmony_ci	/* Compute the performance counter control word. */
708c2ecf20Sopenharmony_ci	/* For now count kernel and user mode */
718c2ecf20Sopenharmony_ci	if (ctr[0].enabled) {
728c2ecf20Sopenharmony_ci		control1 |= LOONGSON3_PERFCTRL_EVENT(0, ctr[0].event) |
738c2ecf20Sopenharmony_ci					LOONGSON3_PERFCTRL_ENABLE;
748c2ecf20Sopenharmony_ci		if (ctr[0].kernel)
758c2ecf20Sopenharmony_ci			control1 |= LOONGSON3_PERFCTRL_KERNEL;
768c2ecf20Sopenharmony_ci		if (ctr[0].user)
778c2ecf20Sopenharmony_ci			control1 |= LOONGSON3_PERFCTRL_USER;
788c2ecf20Sopenharmony_ci		reg.reset_counter1 = 0x8000000000000000ULL - ctr[0].count;
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	if (ctr[1].enabled) {
828c2ecf20Sopenharmony_ci		control2 |= LOONGSON3_PERFCTRL_EVENT(1, ctr[1].event) |
838c2ecf20Sopenharmony_ci					LOONGSON3_PERFCTRL_ENABLE;
848c2ecf20Sopenharmony_ci		if (ctr[1].kernel)
858c2ecf20Sopenharmony_ci			control2 |= LOONGSON3_PERFCTRL_KERNEL;
868c2ecf20Sopenharmony_ci		if (ctr[1].user)
878c2ecf20Sopenharmony_ci			control2 |= LOONGSON3_PERFCTRL_USER;
888c2ecf20Sopenharmony_ci		reg.reset_counter2 = 0x8000000000000000ULL - ctr[1].count;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	if (ctr[0].enabled)
928c2ecf20Sopenharmony_ci		control1 |= LOONGSON3_PERFCTRL_EXL;
938c2ecf20Sopenharmony_ci	if (ctr[1].enabled)
948c2ecf20Sopenharmony_ci		control2 |= LOONGSON3_PERFCTRL_EXL;
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	reg.control1 = control1;
978c2ecf20Sopenharmony_ci	reg.control2 = control2;
988c2ecf20Sopenharmony_ci	reg.ctr1_enable = ctr[0].enabled;
998c2ecf20Sopenharmony_ci	reg.ctr2_enable = ctr[1].enabled;
1008c2ecf20Sopenharmony_ci}
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci/* Program all of the registers in preparation for enabling profiling. */
1038c2ecf20Sopenharmony_cistatic void loongson3_cpu_setup(void *args)
1048c2ecf20Sopenharmony_ci{
1058c2ecf20Sopenharmony_ci	uint64_t perfcount1, perfcount2;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	perfcount1 = reg.reset_counter1;
1088c2ecf20Sopenharmony_ci	perfcount2 = reg.reset_counter2;
1098c2ecf20Sopenharmony_ci	write_c0_perfhi1(perfcount1);
1108c2ecf20Sopenharmony_ci	write_c0_perfhi2(perfcount2);
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic void loongson3_cpu_start(void *args)
1148c2ecf20Sopenharmony_ci{
1158c2ecf20Sopenharmony_ci	/* Start all counters on current CPU */
1168c2ecf20Sopenharmony_ci	reg.control1 |= (LOONGSON3_PERFCTRL_W|LOONGSON3_PERFCTRL_M);
1178c2ecf20Sopenharmony_ci	reg.control2 |= (LOONGSON3_PERFCTRL_W|LOONGSON3_PERFCTRL_M);
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	if (reg.ctr1_enable)
1208c2ecf20Sopenharmony_ci		write_c0_perflo1(reg.control1);
1218c2ecf20Sopenharmony_ci	if (reg.ctr2_enable)
1228c2ecf20Sopenharmony_ci		write_c0_perflo2(reg.control2);
1238c2ecf20Sopenharmony_ci}
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_cistatic void loongson3_cpu_stop(void *args)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	/* Stop all counters on current CPU */
1288c2ecf20Sopenharmony_ci	write_c0_perflo1(0xc0000000);
1298c2ecf20Sopenharmony_ci	write_c0_perflo2(0x40000000);
1308c2ecf20Sopenharmony_ci	memset(&reg, 0, sizeof(reg));
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic int loongson3_perfcount_handler(void)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	unsigned long flags;
1368c2ecf20Sopenharmony_ci	uint64_t counter1, counter2;
1378c2ecf20Sopenharmony_ci	uint32_t cause, handled = IRQ_NONE;
1388c2ecf20Sopenharmony_ci	struct pt_regs *regs = get_irq_regs();
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	cause = read_c0_cause();
1418c2ecf20Sopenharmony_ci	if (!(cause & CAUSEF_PCI))
1428c2ecf20Sopenharmony_ci		return handled;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	counter1 = read_c0_perfhi1();
1458c2ecf20Sopenharmony_ci	counter2 = read_c0_perfhi2();
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	local_irq_save(flags);
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	if (counter1 & LOONGSON3_PERFCNT_OVERFLOW) {
1508c2ecf20Sopenharmony_ci		if (reg.ctr1_enable)
1518c2ecf20Sopenharmony_ci			oprofile_add_sample(regs, 0);
1528c2ecf20Sopenharmony_ci		counter1 = reg.reset_counter1;
1538c2ecf20Sopenharmony_ci	}
1548c2ecf20Sopenharmony_ci	if (counter2 & LOONGSON3_PERFCNT_OVERFLOW) {
1558c2ecf20Sopenharmony_ci		if (reg.ctr2_enable)
1568c2ecf20Sopenharmony_ci			oprofile_add_sample(regs, 1);
1578c2ecf20Sopenharmony_ci		counter2 = reg.reset_counter2;
1588c2ecf20Sopenharmony_ci	}
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	local_irq_restore(flags);
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	write_c0_perfhi1(counter1);
1638c2ecf20Sopenharmony_ci	write_c0_perfhi2(counter2);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	if (!(cause & CAUSEF_TI))
1668c2ecf20Sopenharmony_ci		handled = IRQ_HANDLED;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	return handled;
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistatic int loongson3_starting_cpu(unsigned int cpu)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	write_c0_perflo1(reg.control1);
1748c2ecf20Sopenharmony_ci	write_c0_perflo2(reg.control2);
1758c2ecf20Sopenharmony_ci	return 0;
1768c2ecf20Sopenharmony_ci}
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_cistatic int loongson3_dying_cpu(unsigned int cpu)
1798c2ecf20Sopenharmony_ci{
1808c2ecf20Sopenharmony_ci	write_c0_perflo1(0xc0000000);
1818c2ecf20Sopenharmony_ci	write_c0_perflo2(0x40000000);
1828c2ecf20Sopenharmony_ci	return 0;
1838c2ecf20Sopenharmony_ci}
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_cistatic int __init loongson3_init(void)
1868c2ecf20Sopenharmony_ci{
1878c2ecf20Sopenharmony_ci	on_each_cpu(reset_counters, NULL, 1);
1888c2ecf20Sopenharmony_ci	cpuhp_setup_state_nocalls(CPUHP_AP_MIPS_OP_LOONGSON3_STARTING,
1898c2ecf20Sopenharmony_ci				  "mips/oprofile/loongson3:starting",
1908c2ecf20Sopenharmony_ci				  loongson3_starting_cpu, loongson3_dying_cpu);
1918c2ecf20Sopenharmony_ci	save_perf_irq = perf_irq;
1928c2ecf20Sopenharmony_ci	perf_irq = loongson3_perfcount_handler;
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return 0;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_cistatic void loongson3_exit(void)
1988c2ecf20Sopenharmony_ci{
1998c2ecf20Sopenharmony_ci	on_each_cpu(reset_counters, NULL, 1);
2008c2ecf20Sopenharmony_ci	cpuhp_remove_state_nocalls(CPUHP_AP_MIPS_OP_LOONGSON3_STARTING);
2018c2ecf20Sopenharmony_ci	perf_irq = save_perf_irq;
2028c2ecf20Sopenharmony_ci}
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_cistruct op_mips_model op_model_loongson3_ops = {
2058c2ecf20Sopenharmony_ci	.reg_setup	= loongson3_reg_setup,
2068c2ecf20Sopenharmony_ci	.cpu_setup	= loongson3_cpu_setup,
2078c2ecf20Sopenharmony_ci	.init		= loongson3_init,
2088c2ecf20Sopenharmony_ci	.exit		= loongson3_exit,
2098c2ecf20Sopenharmony_ci	.cpu_start	= loongson3_cpu_start,
2108c2ecf20Sopenharmony_ci	.cpu_stop	= loongson3_cpu_stop,
2118c2ecf20Sopenharmony_ci	.cpu_type	= "mips/loongson3",
2128c2ecf20Sopenharmony_ci	.num_counters	= 2
2138c2ecf20Sopenharmony_ci};
214