diff options
author | Haavard Skinnemoen <hskinnemoen@atmel.com> | 2006-01-04 11:26:23 -0500 |
---|---|---|
committer | Haavard Skinnemoen <hskinnemoen@atmel.com> | 2008-01-25 02:31:40 -0500 |
commit | 2853ce5ecefe9d9ca119f33db0c23a3f075e35d9 (patch) | |
tree | c321226cab8ab804411c1fd8d74fa88b53d5f165 /arch/avr32 | |
parent | a7f5bf9b95ace39cd8b61e8c9ca1035966779ed1 (diff) |
[AVR32] Oprofile support
This adds the necessary architecture code to run oprofile on AVR32
using the performance counters documented by the AVR32 Architecture
Manual.
Signed-off-by: Haavard Skinnemoen <hskinnemoen@atmel.com>
Acked-by: Philippe Elie <phil.el@wanadoo.fr>
Diffstat (limited to 'arch/avr32')
-rw-r--r-- | arch/avr32/Kconfig | 3 | ||||
-rw-r--r-- | arch/avr32/Makefile | 1 | ||||
-rw-r--r-- | arch/avr32/oprofile/Makefile | 8 | ||||
-rw-r--r-- | arch/avr32/oprofile/op_model_avr32.c | 235 |
4 files changed, 247 insertions, 0 deletions
diff --git a/arch/avr32/Kconfig b/arch/avr32/Kconfig index bc08c7f0883f..767a19cfa423 100644 --- a/arch/avr32/Kconfig +++ b/arch/avr32/Kconfig | |||
@@ -54,6 +54,9 @@ config ARCH_HAS_ILOG2_U32 | |||
54 | config ARCH_HAS_ILOG2_U64 | 54 | config ARCH_HAS_ILOG2_U64 |
55 | def_bool n | 55 | def_bool n |
56 | 56 | ||
57 | config ARCH_SUPPORTS_OPROFILE | ||
58 | def_bool y | ||
59 | |||
57 | config GENERIC_HWEIGHT | 60 | config GENERIC_HWEIGHT |
58 | def_bool y | 61 | def_bool y |
59 | 62 | ||
diff --git a/arch/avr32/Makefile b/arch/avr32/Makefile index 87918647be6d..f75d52cd2a4c 100644 --- a/arch/avr32/Makefile +++ b/arch/avr32/Makefile | |||
@@ -31,6 +31,7 @@ core-$(CONFIG_BOARD_ATNGW100) += arch/avr32/boards/atngw100/ | |||
31 | core-$(CONFIG_LOADER_U_BOOT) += arch/avr32/boot/u-boot/ | 31 | core-$(CONFIG_LOADER_U_BOOT) += arch/avr32/boot/u-boot/ |
32 | core-y += arch/avr32/kernel/ | 32 | core-y += arch/avr32/kernel/ |
33 | core-y += arch/avr32/mm/ | 33 | core-y += arch/avr32/mm/ |
34 | drivers-$(CONFIG_OPROFILE) += arch/avr32/oprofile/ | ||
34 | libs-y += arch/avr32/lib/ | 35 | libs-y += arch/avr32/lib/ |
35 | 36 | ||
36 | archincdir-$(CONFIG_PLATFORM_AT32AP) := arch-at32ap | 37 | archincdir-$(CONFIG_PLATFORM_AT32AP) := arch-at32ap |
diff --git a/arch/avr32/oprofile/Makefile b/arch/avr32/oprofile/Makefile new file mode 100644 index 000000000000..1fe81c3c1e86 --- /dev/null +++ b/arch/avr32/oprofile/Makefile | |||
@@ -0,0 +1,8 @@ | |||
1 | obj-$(CONFIG_OPROFILE) += oprofile.o | ||
2 | |||
3 | oprofile-y := $(addprefix ../../../drivers/oprofile/, \ | ||
4 | oprof.o cpu_buffer.o buffer_sync.o \ | ||
5 | event_buffer.o oprofile_files.o \ | ||
6 | oprofilefs.o oprofile_stats.o \ | ||
7 | timer_int.o) | ||
8 | oprofile-y += op_model_avr32.o | ||
diff --git a/arch/avr32/oprofile/op_model_avr32.c b/arch/avr32/oprofile/op_model_avr32.c new file mode 100644 index 000000000000..e2f876bfc86b --- /dev/null +++ b/arch/avr32/oprofile/op_model_avr32.c | |||
@@ -0,0 +1,235 @@ | |||
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/intc.h> | ||
20 | #include <asm/sysreg.h> | ||
21 | #include <asm/system.h> | ||
22 | |||
23 | #define AVR32_PERFCTR_IRQ_GROUP 0 | ||
24 | #define AVR32_PERFCTR_IRQ_LINE 1 | ||
25 | |||
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 | |||
100 | static int avr32_perf_counter_create_files(struct super_block *sb, | ||
101 | struct dentry *root) | ||
102 | { | ||
103 | struct dentry *dir; | ||
104 | unsigned int i; | ||
105 | char filename[4]; | ||
106 | |||
107 | for (i = 0; i < NR_counter; i++) { | ||
108 | snprintf(filename, sizeof(filename), "%u", i); | ||
109 | dir = oprofilefs_mkdir(sb, root, filename); | ||
110 | |||
111 | oprofilefs_create_ulong(sb, dir, "enabled", | ||
112 | &counter[i].enabled); | ||
113 | oprofilefs_create_ulong(sb, dir, "event", | ||
114 | &counter[i].event); | ||
115 | oprofilefs_create_ulong(sb, dir, "count", | ||
116 | &counter[i].count); | ||
117 | |||
118 | /* Dummy entries */ | ||
119 | oprofilefs_create_ulong(sb, dir, "kernel", | ||
120 | &counter[i].kernel); | ||
121 | oprofilefs_create_ulong(sb, dir, "user", | ||
122 | &counter[i].user); | ||
123 | oprofilefs_create_ulong(sb, dir, "unit_mask", | ||
124 | &counter[i].unit_mask); | ||
125 | } | ||
126 | |||
127 | return 0; | ||
128 | } | ||
129 | |||
130 | static int avr32_perf_counter_setup(void) | ||
131 | { | ||
132 | struct avr32_perf_counter *ctr; | ||
133 | u32 pccr; | ||
134 | int ret; | ||
135 | int i; | ||
136 | |||
137 | pr_debug("avr32_perf_counter_setup\n"); | ||
138 | |||
139 | if (sysreg_read(PCCR) & SYSREG_BIT(PCCR_E)) { | ||
140 | printk(KERN_ERR | ||
141 | "oprofile: setup: perf counter already enabled\n"); | ||
142 | return -EBUSY; | ||
143 | } | ||
144 | |||
145 | ret = request_irq(AVR32_PERFCTR_IRQ_GROUP, | ||
146 | avr32_perf_counter_interrupt, IRQF_SHARED, | ||
147 | "oprofile", counter); | ||
148 | if (ret) | ||
149 | return ret; | ||
150 | |||
151 | avr32_perf_counter_reset(); | ||
152 | |||
153 | pccr = 0; | ||
154 | for (i = PCCNT; i < NR_counter; i++) { | ||
155 | ctr = &counter[i]; | ||
156 | if (!ctr->enabled) | ||
157 | continue; | ||
158 | |||
159 | pr_debug("enabling counter %d...\n", i); | ||
160 | |||
161 | pccr |= ctr->ie_mask; | ||
162 | |||
163 | switch (i) { | ||
164 | case PCCNT: | ||
165 | /* PCCNT always counts cycles, so no events */ | ||
166 | sysreg_write(PCCNT, -ctr->count); | ||
167 | break; | ||
168 | case PCNT0: | ||
169 | pccr |= SYSREG_BF(CONF0, ctr->event); | ||
170 | sysreg_write(PCNT0, -ctr->count); | ||
171 | break; | ||
172 | case PCNT1: | ||
173 | pccr |= SYSREG_BF(CONF1, ctr->event); | ||
174 | sysreg_write(PCNT1, -ctr->count); | ||
175 | break; | ||
176 | } | ||
177 | } | ||
178 | |||
179 | pr_debug("oprofile: writing 0x%x to PCCR...\n", pccr); | ||
180 | |||
181 | sysreg_write(PCCR, pccr); | ||
182 | |||
183 | return 0; | ||
184 | } | ||
185 | |||
186 | static void avr32_perf_counter_shutdown(void) | ||
187 | { | ||
188 | pr_debug("avr32_perf_counter_shutdown\n"); | ||
189 | |||
190 | avr32_perf_counter_reset(); | ||
191 | free_irq(AVR32_PERFCTR_IRQ_GROUP, counter); | ||
192 | } | ||
193 | |||
194 | static int avr32_perf_counter_start(void) | ||
195 | { | ||
196 | pr_debug("avr32_perf_counter_start\n"); | ||
197 | |||
198 | sysreg_write(PCCR, sysreg_read(PCCR) | SYSREG_BIT(PCCR_E)); | ||
199 | |||
200 | return 0; | ||
201 | } | ||
202 | |||
203 | static void avr32_perf_counter_stop(void) | ||
204 | { | ||
205 | pr_debug("avr32_perf_counter_stop\n"); | ||
206 | |||
207 | sysreg_write(PCCR, sysreg_read(PCCR) & ~SYSREG_BIT(PCCR_E)); | ||
208 | } | ||
209 | |||
210 | static struct oprofile_operations avr32_perf_counter_ops __initdata = { | ||
211 | .create_files = avr32_perf_counter_create_files, | ||
212 | .setup = avr32_perf_counter_setup, | ||
213 | .shutdown = avr32_perf_counter_shutdown, | ||
214 | .start = avr32_perf_counter_start, | ||
215 | .stop = avr32_perf_counter_stop, | ||
216 | .cpu_type = "avr32", | ||
217 | }; | ||
218 | |||
219 | int __init oprofile_arch_init(struct oprofile_operations *ops) | ||
220 | { | ||
221 | if (!(current_cpu_data.features & AVR32_FEATURE_PCTR)) | ||
222 | return -ENODEV; | ||
223 | |||
224 | memcpy(ops, &avr32_perf_counter_ops, | ||
225 | sizeof(struct oprofile_operations)); | ||
226 | |||
227 | printk(KERN_INFO "oprofile: using AVR32 performance monitoring.\n"); | ||
228 | |||
229 | return 0; | ||
230 | } | ||
231 | |||
232 | void oprofile_arch_exit(void) | ||
233 | { | ||
234 | |||
235 | } | ||