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 | 19 | #include <asm/sysreg.h> |
2853ce5e HS |
20 | |
21 | #define AVR32_PERFCTR_IRQ_GROUP 0 | |
22 | #define AVR32_PERFCTR_IRQ_LINE 1 | |
23 | ||
3d256151 NV |
24 | void avr32_backtrace(struct pt_regs * const regs, unsigned int depth); |
25 | ||
2853ce5e HS |
26 | enum { PCCNT, PCNT0, PCNT1, NR_counter }; |
27 | ||
28 | struct 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 | ||
40 | static 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 | ||
53 | static 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 | ||
63 | static 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 | ||
ef7bca14 | 100 | static int avr32_perf_counter_create_files(struct dentry *root) |
2853ce5e HS |
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); | |
ef7bca14 | 108 | dir = oprofilefs_mkdir(root->d_sb, root, filename); |
2853ce5e | 109 | |
ef7bca14 | 110 | oprofilefs_create_ulong(root->d_sb, dir, "enabled", |
2853ce5e | 111 | &counter[i].enabled); |
ef7bca14 | 112 | oprofilefs_create_ulong(root->d_sb, dir, "event", |
2853ce5e | 113 | &counter[i].event); |
ef7bca14 | 114 | oprofilefs_create_ulong(root->d_sb, dir, "count", |
2853ce5e HS |
115 | &counter[i].count); |
116 | ||
117 | /* Dummy entries */ | |
ef7bca14 | 118 | oprofilefs_create_ulong(root->d_sb, dir, "kernel", |
2853ce5e | 119 | &counter[i].kernel); |
ef7bca14 | 120 | oprofilefs_create_ulong(root->d_sb, dir, "user", |
2853ce5e | 121 | &counter[i].user); |
ef7bca14 | 122 | oprofilefs_create_ulong(root->d_sb, dir, "unit_mask", |
2853ce5e HS |
123 | &counter[i].unit_mask); |
124 | } | |
125 | ||
126 | return 0; | |
127 | } | |
128 | ||
129 | static 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 | ||
185 | static 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 | ||
193 | static 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 | ||
202 | static 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 | ||
209 | static 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 | ||
218 | int __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 | ||
3d256151 NV |
226 | ops->backtrace = avr32_backtrace; |
227 | ||
2853ce5e HS |
228 | printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n"); |
229 | ||
230 | return 0; | |
231 | } | |
232 | ||
233 | void oprofile_arch_exit(void) | |
234 | { | |
235 | ||
236 | } |