1/*
2 * AVR32 Performance Counter Driver
3 *
4 * Copyright (C) 2005-2007 Atmel Corporation
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2 as
8 * published by the Free Software Foundation.
9 *
10 * Author: Ronny Pedersen
11 */
12#include <linux/errno.h>
13#include <linux/interrupt.h>
14#include <linux/irq.h>
15#include <linux/oprofile.h>
16#include <linux/sched.h>
17#include <linux/types.h>
18
19#include <asm/sysreg.h>
20
21#define AVR32_PERFCTR_IRQ_GROUP	0
22#define AVR32_PERFCTR_IRQ_LINE	1
23
24void avr32_backtrace(struct pt_regs * const regs, unsigned int depth);
25
26enum { PCCNT, PCNT0, PCNT1, NR_counter };
27
28struct avr32_perf_counter {
29	unsigned long	enabled;
30	unsigned long	event;
31	unsigned long	count;
32	unsigned long	unit_mask;
33	unsigned long	kernel;
34	unsigned long	user;
35
36	u32		ie_mask;
37	u32		flag_mask;
38};
39
40static struct avr32_perf_counter counter[NR_counter] = {
41	{
42		.ie_mask	= SYSREG_BIT(IEC),
43		.flag_mask	= SYSREG_BIT(FC),
44	}, {
45		.ie_mask	= SYSREG_BIT(IE0),
46		.flag_mask	= SYSREG_BIT(F0),
47	}, {
48		.ie_mask	= SYSREG_BIT(IE1),
49		.flag_mask	= SYSREG_BIT(F1),
50	},
51};
52
53static void avr32_perf_counter_reset(void)
54{
55	/* Reset all counter and disable/clear all interrupts */
56	sysreg_write(PCCR, (SYSREG_BIT(PCCR_R)
57				| SYSREG_BIT(PCCR_C)
58				| SYSREG_BIT(FC)
59				| SYSREG_BIT(F0)
60				| SYSREG_BIT(F1)));
61}
62
63static irqreturn_t avr32_perf_counter_interrupt(int irq, void *dev_id)
64{
65	struct avr32_perf_counter *ctr = dev_id;
66	struct pt_regs *regs;
67	u32 pccr;
68
69	if (likely(!(intc_get_pending(AVR32_PERFCTR_IRQ_GROUP)
70					& (1 << AVR32_PERFCTR_IRQ_LINE))))
71		return IRQ_NONE;
72
73	regs = get_irq_regs();
74	pccr = sysreg_read(PCCR);
75
76	/* Clear the interrupt flags we're about to handle */
77	sysreg_write(PCCR, pccr);
78
79	/* PCCNT */
80	if (ctr->enabled && (pccr & ctr->flag_mask)) {
81		sysreg_write(PCCNT, -ctr->count);
82		oprofile_add_sample(regs, PCCNT);
83	}
84	ctr++;
85	/* PCNT0 */
86	if (ctr->enabled && (pccr & ctr->flag_mask)) {
87		sysreg_write(PCNT0, -ctr->count);
88		oprofile_add_sample(regs, PCNT0);
89	}
90	ctr++;
91	/* PCNT1 */
92	if (ctr->enabled && (pccr & ctr->flag_mask)) {
93		sysreg_write(PCNT1, -ctr->count);
94		oprofile_add_sample(regs, PCNT1);
95	}
96
97	return IRQ_HANDLED;
98}
99
100static int avr32_perf_counter_create_files(struct dentry *root)
101{
102	struct dentry *dir;
103	unsigned int i;
104	char filename[4];
105
106	for (i = 0; i < NR_counter; i++) {
107		snprintf(filename, sizeof(filename), "%u", i);
108		dir = oprofilefs_mkdir(root, filename);
109
110		oprofilefs_create_ulong(dir, "enabled",
111				&counter[i].enabled);
112		oprofilefs_create_ulong(dir, "event",
113				&counter[i].event);
114		oprofilefs_create_ulong(dir, "count",
115				&counter[i].count);
116
117		/* Dummy entries */
118		oprofilefs_create_ulong(dir, "kernel",
119				&counter[i].kernel);
120		oprofilefs_create_ulong(dir, "user",
121				&counter[i].user);
122		oprofilefs_create_ulong(dir, "unit_mask",
123				&counter[i].unit_mask);
124	}
125
126	return 0;
127}
128
129static int avr32_perf_counter_setup(void)
130{
131	struct avr32_perf_counter *ctr;
132	u32 pccr;
133	int ret;
134	int i;
135
136	pr_debug("avr32_perf_counter_setup\n");
137
138	if (sysreg_read(PCCR) & SYSREG_BIT(PCCR_E)) {
139		printk(KERN_ERR
140			"oprofile: setup: perf counter already enabled\n");
141		return -EBUSY;
142	}
143
144	ret = request_irq(AVR32_PERFCTR_IRQ_GROUP,
145			avr32_perf_counter_interrupt, IRQF_SHARED,
146			"oprofile", counter);
147	if (ret)
148		return ret;
149
150	avr32_perf_counter_reset();
151
152	pccr = 0;
153	for (i = PCCNT; i < NR_counter; i++) {
154		ctr = &counter[i];
155		if (!ctr->enabled)
156			continue;
157
158		pr_debug("enabling counter %d...\n", i);
159
160		pccr |= ctr->ie_mask;
161
162		switch (i) {
163		case PCCNT:
164			/* PCCNT always counts cycles, so no events */
165			sysreg_write(PCCNT, -ctr->count);
166			break;
167		case PCNT0:
168			pccr |= SYSREG_BF(CONF0, ctr->event);
169			sysreg_write(PCNT0, -ctr->count);
170			break;
171		case PCNT1:
172			pccr |= SYSREG_BF(CONF1, ctr->event);
173			sysreg_write(PCNT1, -ctr->count);
174			break;
175		}
176	}
177
178	pr_debug("oprofile: writing 0x%x to PCCR...\n", pccr);
179
180	sysreg_write(PCCR, pccr);
181
182	return 0;
183}
184
185static void avr32_perf_counter_shutdown(void)
186{
187	pr_debug("avr32_perf_counter_shutdown\n");
188
189	avr32_perf_counter_reset();
190	free_irq(AVR32_PERFCTR_IRQ_GROUP, counter);
191}
192
193static int avr32_perf_counter_start(void)
194{
195	pr_debug("avr32_perf_counter_start\n");
196
197	sysreg_write(PCCR, sysreg_read(PCCR) | SYSREG_BIT(PCCR_E));
198
199	return 0;
200}
201
202static void avr32_perf_counter_stop(void)
203{
204	pr_debug("avr32_perf_counter_stop\n");
205
206	sysreg_write(PCCR, sysreg_read(PCCR) & ~SYSREG_BIT(PCCR_E));
207}
208
209static struct oprofile_operations avr32_perf_counter_ops __initdata = {
210	.create_files	= avr32_perf_counter_create_files,
211	.setup		= avr32_perf_counter_setup,
212	.shutdown	= avr32_perf_counter_shutdown,
213	.start		= avr32_perf_counter_start,
214	.stop		= avr32_perf_counter_stop,
215	.cpu_type	= "avr32",
216};
217
218int __init oprofile_arch_init(struct oprofile_operations *ops)
219{
220	if (!(current_cpu_data.features & AVR32_FEATURE_PCTR))
221		return -ENODEV;
222
223	memcpy(ops, &avr32_perf_counter_ops,
224			sizeof(struct oprofile_operations));
225
226	ops->backtrace = avr32_backtrace;
227
228	printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n");
229
230	return 0;
231}
232
233void oprofile_arch_exit(void)
234{
235
236}
237