18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Loongson2 performance counter driver for oprofile
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * Copyright (C) 2009 Lemote Inc.
58c2ecf20Sopenharmony_ci * Author: Yanhua <yanh@lemote.com>
68c2ecf20Sopenharmony_ci * Author: Wu Zhangjin <wuzhangjin@gmail.com>
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
98c2ecf20Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
108c2ecf20Sopenharmony_ci * for more details.
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci#include <linux/init.h>
138c2ecf20Sopenharmony_ci#include <linux/oprofile.h>
148c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#include <loongson.h>			/* LOONGSON2_PERFCNT_IRQ */
178c2ecf20Sopenharmony_ci#include "op_impl.h"
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci#define LOONGSON2_CPU_TYPE	"mips/loongson2"
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCNT_OVERFLOW		(1ULL	<< 31)
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_EXL			(1UL	<<  0)
248c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_KERNEL		(1UL	<<  1)
258c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_SUPERVISOR		(1UL	<<  2)
268c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_USER			(1UL	<<  3)
278c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_ENABLE		(1UL	<<  4)
288c2ecf20Sopenharmony_ci#define LOONGSON2_PERFCTRL_EVENT(idx, event) \
298c2ecf20Sopenharmony_ci	(((event) & 0x0f) << ((idx) ? 9 : 5))
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci#define read_c0_perfctrl() __read_64bit_c0_register($24, 0)
328c2ecf20Sopenharmony_ci#define write_c0_perfctrl(val) __write_64bit_c0_register($24, 0, val)
338c2ecf20Sopenharmony_ci#define read_c0_perfcnt() __read_64bit_c0_register($25, 0)
348c2ecf20Sopenharmony_ci#define write_c0_perfcnt(val) __write_64bit_c0_register($25, 0, val)
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic struct loongson2_register_config {
378c2ecf20Sopenharmony_ci	unsigned int ctrl;
388c2ecf20Sopenharmony_ci	unsigned long long reset_counter1;
398c2ecf20Sopenharmony_ci	unsigned long long reset_counter2;
408c2ecf20Sopenharmony_ci	int cnt1_enabled, cnt2_enabled;
418c2ecf20Sopenharmony_ci} reg;
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic char *oprofid = "LoongsonPerf";
448c2ecf20Sopenharmony_cistatic irqreturn_t loongson2_perfcount_handler(int irq, void *dev_id);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_cistatic void reset_counters(void *arg)
478c2ecf20Sopenharmony_ci{
488c2ecf20Sopenharmony_ci	write_c0_perfctrl(0);
498c2ecf20Sopenharmony_ci	write_c0_perfcnt(0);
508c2ecf20Sopenharmony_ci}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_cistatic void loongson2_reg_setup(struct op_counter_config *cfg)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	unsigned int ctrl = 0;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	reg.reset_counter1 = 0;
578c2ecf20Sopenharmony_ci	reg.reset_counter2 = 0;
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_ci	/*
608c2ecf20Sopenharmony_ci	 * Compute the performance counter ctrl word.
618c2ecf20Sopenharmony_ci	 * For now, count kernel and user mode.
628c2ecf20Sopenharmony_ci	 */
638c2ecf20Sopenharmony_ci	if (cfg[0].enabled) {
648c2ecf20Sopenharmony_ci		ctrl |= LOONGSON2_PERFCTRL_EVENT(0, cfg[0].event);
658c2ecf20Sopenharmony_ci		reg.reset_counter1 = 0x80000000ULL - cfg[0].count;
668c2ecf20Sopenharmony_ci	}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	if (cfg[1].enabled) {
698c2ecf20Sopenharmony_ci		ctrl |= LOONGSON2_PERFCTRL_EVENT(1, cfg[1].event);
708c2ecf20Sopenharmony_ci		reg.reset_counter2 = 0x80000000ULL - cfg[1].count;
718c2ecf20Sopenharmony_ci	}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (cfg[0].enabled || cfg[1].enabled) {
748c2ecf20Sopenharmony_ci		ctrl |= LOONGSON2_PERFCTRL_EXL | LOONGSON2_PERFCTRL_ENABLE;
758c2ecf20Sopenharmony_ci		if (cfg[0].kernel || cfg[1].kernel)
768c2ecf20Sopenharmony_ci			ctrl |= LOONGSON2_PERFCTRL_KERNEL;
778c2ecf20Sopenharmony_ci		if (cfg[0].user || cfg[1].user)
788c2ecf20Sopenharmony_ci			ctrl |= LOONGSON2_PERFCTRL_USER;
798c2ecf20Sopenharmony_ci	}
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	reg.ctrl = ctrl;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	reg.cnt1_enabled = cfg[0].enabled;
848c2ecf20Sopenharmony_ci	reg.cnt2_enabled = cfg[1].enabled;
858c2ecf20Sopenharmony_ci}
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_cistatic void loongson2_cpu_setup(void *args)
888c2ecf20Sopenharmony_ci{
898c2ecf20Sopenharmony_ci	write_c0_perfcnt((reg.reset_counter2 << 32) | reg.reset_counter1);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic void loongson2_cpu_start(void *args)
938c2ecf20Sopenharmony_ci{
948c2ecf20Sopenharmony_ci	/* Start all counters on current CPU */
958c2ecf20Sopenharmony_ci	if (reg.cnt1_enabled || reg.cnt2_enabled)
968c2ecf20Sopenharmony_ci		write_c0_perfctrl(reg.ctrl);
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic void loongson2_cpu_stop(void *args)
1008c2ecf20Sopenharmony_ci{
1018c2ecf20Sopenharmony_ci	/* Stop all counters on current CPU */
1028c2ecf20Sopenharmony_ci	write_c0_perfctrl(0);
1038c2ecf20Sopenharmony_ci	memset(&reg, 0, sizeof(reg));
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic irqreturn_t loongson2_perfcount_handler(int irq, void *dev_id)
1078c2ecf20Sopenharmony_ci{
1088c2ecf20Sopenharmony_ci	uint64_t counter, counter1, counter2;
1098c2ecf20Sopenharmony_ci	struct pt_regs *regs = get_irq_regs();
1108c2ecf20Sopenharmony_ci	int enabled;
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	/* Check whether the irq belongs to me */
1138c2ecf20Sopenharmony_ci	enabled = read_c0_perfctrl() & LOONGSON2_PERFCTRL_ENABLE;
1148c2ecf20Sopenharmony_ci	if (!enabled)
1158c2ecf20Sopenharmony_ci		return IRQ_NONE;
1168c2ecf20Sopenharmony_ci	enabled = reg.cnt1_enabled | reg.cnt2_enabled;
1178c2ecf20Sopenharmony_ci	if (!enabled)
1188c2ecf20Sopenharmony_ci		return IRQ_NONE;
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_ci	counter = read_c0_perfcnt();
1218c2ecf20Sopenharmony_ci	counter1 = counter & 0xffffffff;
1228c2ecf20Sopenharmony_ci	counter2 = counter >> 32;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	if (counter1 & LOONGSON2_PERFCNT_OVERFLOW) {
1258c2ecf20Sopenharmony_ci		if (reg.cnt1_enabled)
1268c2ecf20Sopenharmony_ci			oprofile_add_sample(regs, 0);
1278c2ecf20Sopenharmony_ci		counter1 = reg.reset_counter1;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci	if (counter2 & LOONGSON2_PERFCNT_OVERFLOW) {
1308c2ecf20Sopenharmony_ci		if (reg.cnt2_enabled)
1318c2ecf20Sopenharmony_ci			oprofile_add_sample(regs, 1);
1328c2ecf20Sopenharmony_ci		counter2 = reg.reset_counter2;
1338c2ecf20Sopenharmony_ci	}
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	write_c0_perfcnt((counter2 << 32) | counter1);
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1388c2ecf20Sopenharmony_ci}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_cistatic int __init loongson2_init(void)
1418c2ecf20Sopenharmony_ci{
1428c2ecf20Sopenharmony_ci	return request_irq(LOONGSON2_PERFCNT_IRQ, loongson2_perfcount_handler,
1438c2ecf20Sopenharmony_ci			   IRQF_SHARED, "Perfcounter", oprofid);
1448c2ecf20Sopenharmony_ci}
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_cistatic void loongson2_exit(void)
1478c2ecf20Sopenharmony_ci{
1488c2ecf20Sopenharmony_ci	reset_counters(NULL);
1498c2ecf20Sopenharmony_ci	free_irq(LOONGSON2_PERFCNT_IRQ, oprofid);
1508c2ecf20Sopenharmony_ci}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_cistruct op_mips_model op_model_loongson2_ops = {
1538c2ecf20Sopenharmony_ci	.reg_setup = loongson2_reg_setup,
1548c2ecf20Sopenharmony_ci	.cpu_setup = loongson2_cpu_setup,
1558c2ecf20Sopenharmony_ci	.init = loongson2_init,
1568c2ecf20Sopenharmony_ci	.exit = loongson2_exit,
1578c2ecf20Sopenharmony_ci	.cpu_start = loongson2_cpu_start,
1588c2ecf20Sopenharmony_ci	.cpu_stop = loongson2_cpu_stop,
1598c2ecf20Sopenharmony_ci	.cpu_type = LOONGSON2_CPU_TYPE,
1608c2ecf20Sopenharmony_ci	.num_counters = 2
1618c2ecf20Sopenharmony_ci};
162