1/*
2 * Broadcom BCM47xx Performance Counters
3 *
4 * Copyright 2006, Broadcom Corporation
5 * All Rights Reserved.
6 *
7 * THIS SOFTWARE IS OFFERED "AS IS", AND BROADCOM GRANTS NO WARRANTIES OF ANY
8 * KIND, EXPRESS OR IMPLIED, BY STATUTE, COMMUNICATION OR OTHERWISE. BROADCOM
9 * SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS
10 * FOR A SPECIFIC PURPOSE OR NONINFRINGEMENT CONCERNING THIS SOFTWARE.
11 *
12 * $Id: perfcntr.c,v 1.1.1.1 2008/10/15 03:26:06 james26_jang Exp $
13 */
14
15#include <linux/config.h>
16
17#ifdef CONFIG_PROC_FS
18#include <linux/proc_fs.h>
19#include <typedefs.h>
20#include <osl.h>
21#include <mipsinc.h>
22
23/*
24 * BCM4710 performance counter register select values
25 * No even-odd control-counter mapping, just counters
26 */
27#define PERF_DCACHE_HIT		0
28#define PERF_DCACHE_MISS	1
29#define PERF_ICACHE_HIT		2
30#define PERF_ICACHE_MISS	3
31#define PERF_ICOUNT		4
32
33asmlinkage uint read_perf_cntr(uint counter)
34{
35	uint32 prid = MFC0(C0_PRID, 0);
36	if (BCM330X(prid)) {
37#ifdef CONFIG_HND_BMIPS3300_PROF
38		switch (counter) {
39		case PERF_DCACHE_HIT:	return -MFC0(C0_PERFORMANCE, 0);
40		case PERF_DCACHE_MISS:	return -MFC0(C0_PERFORMANCE, 1);
41		case PERF_ICACHE_HIT:	return -MFC0(C0_PERFORMANCE, 2);
42		case PERF_ICACHE_MISS:	return -MFC0(C0_PERFORMANCE, 3);
43		}
44#endif	/* CONFIG_HND_BMIPS3300_PROF */
45		return 0;
46	}
47	else {
48		switch (counter) {
49		case PERF_DCACHE_HIT:	return MFC0(C0_PERFORMANCE, 0);
50		case PERF_DCACHE_MISS:	return MFC0(C0_PERFORMANCE, 1);
51		case PERF_ICACHE_HIT:	return MFC0(C0_PERFORMANCE, 2);
52		case PERF_ICACHE_MISS:	return MFC0(C0_PERFORMANCE, 3);
53		case PERF_ICOUNT:	return MFC0(C0_PERFORMANCE, 4);
54		}
55	}
56	return 0;
57}
58
59/*
60 * 'data' passed to proc entry callback is formatted as:
61 *	(reg << 16) + sel
62 */
63#define REGSHIFT	16	/* register # is at high 16 bit */
64#define SELMASK		0xffff	/* select # is at low 16 bit */
65
66/*
67 * Template to read/write cp0 register. Caller will modify
68 * the mfc0 and mtc0 instructions before calling.
69 */
70static void tmfc0(void)
71{
72	__asm__ __volatile__(
73		".set\tnoreorder\n\t"
74		".set\tnoat\n\t"
75		"mfc0\t$1,$0\n\t"
76		"move\t$2,$1\n\t"
77		".set\tat\n\t"
78		".set\treorder");
79}
80
81static void tmtc0(uint val)
82{
83	__asm__ __volatile__(
84		".set\tnoreorder\n\t"
85		".set\tnoat\n\t"
86		"move\t$1,$4\n\t"
87		"mtc0\t$1,$0\n\t"
88		".set\tat\n\t"
89		".set\treorder");
90}
91
92/*
93 * Read/write cp0 register. Assuming code space is writeable
94 * so modify the mfc0 and mtc0 instructions before calling the
95 * function tmfc0() and tmtc0() to build the correct mfc0 and
96 * mtc0 instructions.
97 */
98static int cp0_read(char *page, char **start, off_t off,
99                    int count, int *eof, void *data)
100{
101	uint reg, sel;
102	uint (*cp0i)(void);
103	size_t len;
104
105	/* we have done once so stop */
106	if (off) {
107		len = 0;
108		goto done;
109	}
110
111	/* change the mfc0 instr with the right reg/sel */
112	reg = (uintptr)data >> REGSHIFT;
113	sel = (uintptr)data & SELMASK;
114	cp0i = (uint (*)(void))KSEG1ADDR(tmfc0);
115	*(uint *)cp0i = 0x40010000 | (reg << 11) | sel;
116	__asm__ __volatile__("nop; nop; nop; nop;");
117
118	/* return the value in hex string */
119	len = sprintf(page, "0x%08x\n", cp0i());
120	*start = page;
121done:	return len;
122}
123
124static int cp0_write(struct file *file, const char *buf,
125                     unsigned long count, void *data)
126{
127	uint reg, sel, val;
128	void (*cp0i)(uint);
129
130	/* change the mtc0 instr with the right reg/sel */
131	reg = (uintptr)data >> REGSHIFT;
132	sel = (uintptr)data & SELMASK;
133	val = simple_strtoul(buf, NULL, 0);
134	cp0i = (void (*)(uint))KSEG1ADDR(tmtc0);
135	*((uint *)cp0i + 1) = 0x40810000 | (reg << 11) | sel;
136	__asm__ __volatile__("nop; nop; nop; nop;");
137
138	/* set the value and we are all done */
139	cp0i(val);
140	return count;
141}
142
143#ifdef CONFIG_HND_BMIPS3300_PROF
144/*
145 * Enable/disable cache hits/misses countings - counters are
146 * hard wired as:
147 *	d$ - cntr 0 and 1
148 *	i$ - cntr 2 and 3
149 * They can't be enabled at the same time according to the BMIPS
150 * 3300 documentations, although they are displayed togather.
151 */
152static void bmips3300_dccntenab(bool enable)
153{
154	if (enable) {
155		MTC0(C0_PERFORMANCE, 6, 0x80000211);	/* enable D$ counting */
156		MTC0(C0_PERFORMANCE, 4, 0x80248028);	/* enable cntr 0 and 1 */
157		MTC0(C0_PERFORMANCE, 0, 0);		/* zero cntr 0 - # hits */
158		MTC0(C0_PERFORMANCE, 1, 0);		/* zero cntr 1 - # misses */
159		printk("enabled performance counter 0 for D$ hits\n");
160		printk("enabled performance counter 1 for D$ misses\n");
161	}
162	else {
163		MTC0(C0_PERFORMANCE, 4, 0);		/* disable cntr 0 and 1 */
164		MTC0(C0_PERFORMANCE, 6, 0);		/* disable D$ counting */
165		printk("disabled performance counters\n");
166	}
167}
168
169static void bmips3300_iccntenab(bool enable)
170{
171	if (enable) {
172		MTC0(C0_PERFORMANCE, 6, 0x80000218);	/* enable I$ counting */
173		MTC0(C0_PERFORMANCE, 5, 0x80148018);	/* enable cntr 2 and 3 */
174		MTC0(C0_PERFORMANCE, 2, 0);		/* zero cntr 0 - # hits */
175		MTC0(C0_PERFORMANCE, 3, 0);		/* zero cntr 1 - # misses */
176		printk("enabled performance counter 2 for I$ hits\n");
177		printk("enabled performance counter 3 for I$ misses\n");
178	}
179	else {
180		MTC0(C0_PERFORMANCE, 5, 0);		/* disable cntr 2 and 3 */
181		MTC0(C0_PERFORMANCE, 6, 0);		/* disable I$ counting */
182		printk("disabled performance counters\n");
183	}
184}
185
186/* cache counting enable/disable proc entry callback */
187#define DCACHE_CNTENAB	0
188#define ICACHE_CNTENAB	1
189
190static int bmips3300_ccntenab(struct file *file, const char *buf,
191                         unsigned long count, void *data)
192{
193	uint val = simple_strtoul(buf, NULL, 0);
194	switch ((uint)data) {
195	case DCACHE_CNTENAB:
196		bmips3300_dccntenab(val != 0);
197		break;
198	case ICACHE_CNTENAB:
199		bmips3300_iccntenab(val != 0);
200		break;
201	}
202	return count;
203}
204#endif	/* CONFIG_HND_BMIPS3300_PROF */
205
206/* cp0 registers/selects map */
207#define MAXREGS		32	/* max # registers */
208#define MAXSELS		32	/* max # selects */
209#define CP0REG(r)	(1<<(r))
210#define CP0SEL(s)	(1<<(s))
211typedef struct
212{
213	uint32 reg_map;		/* registers map */
214	uint8 sel_map[MAXSELS];	/* selects map */
215} cp0_reg_map_t;
216
217#ifdef CONFIG_HND_BMIPS3300_PROF
218static cp0_reg_map_t bmips3300_cp0regmap =
219{
220	CP0REG(0)|CP0REG(1)|CP0REG(2)|CP0REG(3)|CP0REG(4)|CP0REG(5)|
221		CP0REG(6)|CP0REG(7)|CP0REG(8)|CP0REG(9)|CP0REG(10)|
222		CP0REG(11)|CP0REG(12)|CP0REG(13)|CP0REG(14)|CP0REG(15)|
223		CP0REG(16)|CP0REG(22)|CP0REG(23)|CP0REG(24)|CP0REG(25)|
224		CP0REG(28)|CP0REG(30)|CP0REG(31),
225	{
226		/* 0 */	CP0SEL(0),
227		/* 1 */	CP0SEL(0),
228		/* 2 */	CP0SEL(0),
229		/* 3 */	CP0SEL(0),
230		/* 4 */	CP0SEL(0),
231		/* 5 */	CP0SEL(0),
232		/* 6 */	CP0SEL(0),
233		/* 7 */	CP0SEL(0),
234		/* 8 */	CP0SEL(0),
235		/* 9 */	CP0SEL(0),
236		/* 10 */ CP0SEL(0),
237		/* 11 */ CP0SEL(0),
238		/* 12 */ CP0SEL(0),
239		/* 13 */ CP0SEL(0),
240		/* 14 */ CP0SEL(0),
241		/* 15 */ CP0SEL(0),
242		/* 16 */ CP0SEL(0)|CP0SEL(1),
243		0,
244		0,
245		0,
246		0,
247		0,
248		/* 22 */ CP0SEL(0)|CP0SEL(4)|CP0SEL(5),
249		/* 23 */ CP0SEL(0),
250		/* 24 */ CP0SEL(0),
251		/* 25 */ CP0SEL(0)|CP0SEL(1)|CP0SEL(2)|CP0SEL(3)|CP0SEL(4)|
252			CP0SEL(5)|CP0SEL(6),
253		0,
254		0,
255		/* 28 */ CP0SEL(0)|CP0SEL(1),
256		0,
257		/* 30 */ CP0SEL(0),
258		/* 31 */ CP0SEL(0),
259	}
260};
261#endif	/* CONFIG_HND_BMIPS3300_PROF */
262
263/* create/remove proc entries - no worry about error handling;-( */
264static int __init cp0_init(void)
265{
266	struct proc_dir_entry *cp0_proc;
267#ifdef CONFIG_HND_BMIPS3300_PROF
268	struct proc_dir_entry *cache_proc;
269#endif	/* CONFIG_HND_BMIPS3300_PROF */
270	struct proc_dir_entry *reg_proc, *sel_proc;
271	cp0_reg_map_t *reg_map = NULL;
272	uint32 prid;
273	char name[16];
274	int i, j;
275
276	/* create proc entry cp0 in root */
277	cp0_proc = create_proc_entry("cp0", 0444 | S_IFDIR, &proc_root);
278	if (!cp0_proc)
279		return 0;
280
281	/* create proc entries for enabling cache hit/miss counting */
282	prid = MFC0(C0_PRID, 0);
283	if (BCM330X(prid)) {
284#ifdef CONFIG_HND_BMIPS3300_PROF
285		/* D$ */
286		cache_proc = create_proc_entry("dccnt", 0644, cp0_proc);
287		if (!cache_proc)
288			return 0;
289		cache_proc->write_proc = bmips3300_ccntenab;
290		cache_proc->data = (void *)DCACHE_CNTENAB;
291		/* I$ */
292		cache_proc = create_proc_entry("iccnt", 0644, cp0_proc);
293		if (!cache_proc)
294			return 0;
295		cache_proc->write_proc = bmips3300_ccntenab;
296		cache_proc->data = (void *)ICACHE_CNTENAB;
297#endif	/* CONFIG_HND_BMIPS3300_PROF */
298	}
299
300	/* select cp0 registers/selects map table */
301	if (BCM330X(prid)) {
302#ifdef CONFIG_HND_BMIPS3300_PROF
303		reg_map = &bmips3300_cp0regmap;
304#endif	/* CONFIG_HND_BMIPS3300_PROF */
305	}
306	if (!reg_map)
307		return 0;
308	/* create proc entry for each register select */
309	for (i = 0; i < MAXREGS; i ++) {
310		if (!(reg_map->reg_map & (1 << i)))
311			continue;
312		sprintf(name, "%u", i);
313		reg_proc = create_proc_entry(name, 0444 | S_IFDIR, cp0_proc);
314		if (!reg_proc)
315			break;
316		for (j = 0; j < MAXSELS; j ++) {
317			if (!(reg_map->sel_map[i] & (1 << j)))
318				continue;
319			sprintf(name, "%u", j);
320			sel_proc = create_proc_entry(name, 0644, reg_proc);
321			if (!sel_proc)
322				break;
323			sel_proc->read_proc = cp0_read;
324			sel_proc->write_proc = cp0_write;
325			sel_proc->data = (void *)((i << REGSHIFT) + j);
326		}
327	}
328	return 0;
329}
330
331static void __exit cp0_cleanup(void)
332{
333	remove_proc_entry("cp0", &proc_root);
334}
335
336/* hook it up with system at boot time */
337module_init(cp0_init);
338module_exit(cp0_cleanup);
339
340#endif	/* CONFIG_PROC_FS */
341