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