1 /*
2  *  (C) 2010,2011      Thomas Renninger <trenn@suse.de>, Novell Inc.
3  *
4  *  Licensed under the terms of the GNU GPL License version 2.
5  *
6  *  PCI initialization based on example code from:
7  *  Andreas Herrmann <andreas.herrmann3@amd.com>
8  */
9 
10 #if defined(__i386__) || defined(__x86_64__)
11 
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <stdint.h>
15 #include <time.h>
16 #include <string.h>
17 
18 #include <pci/pci.h>
19 
20 #include "idle_monitor/cpupower-monitor.h"
21 #include "helpers/helpers.h"
22 
23 /******** PCI parts could go into own file and get shared ***************/
24 
25 #define PCI_NON_PC0_OFFSET	0xb0
26 #define PCI_PC1_OFFSET		0xb4
27 #define PCI_PC6_OFFSET		0xb8
28 
29 #define PCI_MONITOR_ENABLE_REG  0xe0
30 
31 #define PCI_NON_PC0_ENABLE_BIT	0
32 #define PCI_PC1_ENABLE_BIT	1
33 #define PCI_PC6_ENABLE_BIT	2
34 
35 #define PCI_NBP1_STAT_OFFSET	0x98
36 #define PCI_NBP1_ACTIVE_BIT	2
37 #define PCI_NBP1_ENTERED_BIT	1
38 
39 #define PCI_NBP1_CAP_OFFSET	0x90
40 #define PCI_NBP1_CAPABLE_BIT    31
41 
42 #define OVERFLOW_MS		343597 /* 32 bit register filled at 12500 HZ
43 					  (1 tick per 80ns) */
44 
45 enum amd_fam14h_states {NON_PC0 = 0, PC1, PC6, NBP1,
46 			AMD_FAM14H_STATE_NUM};
47 
48 static int fam14h_get_count_percent(unsigned int self_id, double *percent,
49 				    unsigned int cpu);
50 static int fam14h_nbp1_count(unsigned int id, unsigned long long *count,
51 			     unsigned int cpu);
52 
53 static cstate_t amd_fam14h_cstates[AMD_FAM14H_STATE_NUM] = {
54 	{
55 		.name			= "!PC0",
56 		.desc			= N_("Package in sleep state (PC1 or deeper)"),
57 		.id			= NON_PC0,
58 		.range			= RANGE_PACKAGE,
59 		.get_count_percent	= fam14h_get_count_percent,
60 	},
61 	{
62 		.name			= "PC1",
63 		.desc			= N_("Processor Package C1"),
64 		.id			= PC1,
65 		.range			= RANGE_PACKAGE,
66 		.get_count_percent	= fam14h_get_count_percent,
67 	},
68 	{
69 		.name			= "PC6",
70 		.desc			= N_("Processor Package C6"),
71 		.id			= PC6,
72 		.range			= RANGE_PACKAGE,
73 		.get_count_percent	= fam14h_get_count_percent,
74 	},
75 	{
76 		.name			= "NBP1",
77 		.desc			= N_("North Bridge P1 boolean counter (returns 0 or 1)"),
78 		.id			= NBP1,
79 		.range			= RANGE_PACKAGE,
80 		.get_count		= fam14h_nbp1_count,
81 	},
82 };
83 
84 static struct pci_access *pci_acc;
85 static int pci_vendor_id = 0x1022;
86 static int pci_dev_ids[2] = {0x1716, 0};
87 static struct pci_dev *amd_fam14h_pci_dev;
88 
89 static int nbp1_entered;
90 
91 struct timespec start_time;
92 static unsigned long long timediff;
93 
94 #ifdef DEBUG
95 struct timespec dbg_time;
96 long dbg_timediff;
97 #endif
98 
99 static unsigned long long *previous_count[AMD_FAM14H_STATE_NUM];
100 static unsigned long long *current_count[AMD_FAM14H_STATE_NUM];
101 
amd_fam14h_get_pci_info(struct cstate * state,unsigned int * pci_offset,unsigned int * enable_bit,unsigned int cpu)102 static int amd_fam14h_get_pci_info(struct cstate *state,
103 				   unsigned int *pci_offset,
104 				   unsigned int *enable_bit,
105 				   unsigned int cpu)
106 {
107 	switch (state->id) {
108 	case NON_PC0:
109 		*enable_bit = PCI_NON_PC0_ENABLE_BIT;
110 		*pci_offset = PCI_NON_PC0_OFFSET;
111 		break;
112 	case PC1:
113 		*enable_bit = PCI_PC1_ENABLE_BIT;
114 		*pci_offset = PCI_PC1_OFFSET;
115 		break;
116 	case PC6:
117 		*enable_bit = PCI_PC6_ENABLE_BIT;
118 		*pci_offset = PCI_PC6_OFFSET;
119 		break;
120 	case NBP1:
121 		*enable_bit = PCI_NBP1_ENTERED_BIT;
122 		*pci_offset = PCI_NBP1_STAT_OFFSET;
123 		break;
124 	default:
125 		return -1;
126 	};
127 	return 0;
128 }
129 
amd_fam14h_init(cstate_t * state,unsigned int cpu)130 static int amd_fam14h_init(cstate_t *state, unsigned int cpu)
131 {
132 	int enable_bit, pci_offset, ret;
133 	uint32_t val;
134 
135 	ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu);
136 	if (ret)
137 		return ret;
138 
139 	/* NBP1 needs extra treating -> write 1 to D18F6x98 bit 1 for init */
140 	if (state->id == NBP1) {
141 		val = pci_read_long(amd_fam14h_pci_dev, pci_offset);
142 		val |= 1 << enable_bit;
143 		val = pci_write_long(amd_fam14h_pci_dev, pci_offset, val);
144 		return ret;
145 	}
146 
147 	/* Enable monitor */
148 	val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG);
149 	dprint("Init %s: read at offset: 0x%x val: %u\n", state->name,
150 	       PCI_MONITOR_ENABLE_REG, (unsigned int) val);
151 	val |= 1 << enable_bit;
152 	pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val);
153 
154 	dprint("Init %s: offset: 0x%x enable_bit: %d - val: %u (%u)\n",
155 	       state->name, PCI_MONITOR_ENABLE_REG, enable_bit,
156 	       (unsigned int) val, cpu);
157 
158 	/* Set counter to zero */
159 	pci_write_long(amd_fam14h_pci_dev, pci_offset, 0);
160 	previous_count[state->id][cpu] = 0;
161 
162 	return 0;
163 }
164 
amd_fam14h_disable(cstate_t * state,unsigned int cpu)165 static int amd_fam14h_disable(cstate_t *state, unsigned int cpu)
166 {
167 	int enable_bit, pci_offset, ret;
168 	uint32_t val;
169 
170 	ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu);
171 	if (ret)
172 		return ret;
173 
174 	val = pci_read_long(amd_fam14h_pci_dev, pci_offset);
175 	dprint("%s: offset: 0x%x %u\n", state->name, pci_offset, val);
176 	if (state->id == NBP1) {
177 		/* was the bit whether NBP1 got entered set? */
178 		nbp1_entered = (val & (1 << PCI_NBP1_ACTIVE_BIT)) |
179 			(val & (1 << PCI_NBP1_ENTERED_BIT));
180 
181 		dprint("NBP1 was %sentered - 0x%x - enable_bit: "
182 		       "%d - pci_offset: 0x%x\n",
183 		       nbp1_entered ? "" : "not ",
184 		       val, enable_bit, pci_offset);
185 		return ret;
186 	}
187 	current_count[state->id][cpu] = val;
188 
189 	dprint("%s: Current -  %llu (%u)\n", state->name,
190 	       current_count[state->id][cpu], cpu);
191 	dprint("%s: Previous - %llu (%u)\n", state->name,
192 	       previous_count[state->id][cpu], cpu);
193 
194 	val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG);
195 	val &= ~(1 << enable_bit);
196 	pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val);
197 
198 	return 0;
199 }
200 
fam14h_nbp1_count(unsigned int id,unsigned long long * count,unsigned int cpu)201 static int fam14h_nbp1_count(unsigned int id, unsigned long long *count,
202 			     unsigned int cpu)
203 {
204 	if (id == NBP1) {
205 		if (nbp1_entered)
206 			*count = 1;
207 		else
208 			*count = 0;
209 		return 0;
210 	}
211 	return -1;
212 }
fam14h_get_count_percent(unsigned int id,double * percent,unsigned int cpu)213 static int fam14h_get_count_percent(unsigned int id, double *percent,
214 				    unsigned int cpu)
215 {
216 	unsigned long diff;
217 
218 	if (id >= AMD_FAM14H_STATE_NUM)
219 		return -1;
220 	/* residency count in 80ns -> divide through 12.5 to get us residency */
221 	diff = current_count[id][cpu] - previous_count[id][cpu];
222 
223 	if (timediff == 0)
224 		*percent = 0.0;
225 	else
226 		*percent = 100.0 * diff / timediff / 12.5;
227 
228 	dprint("Timediff: %llu - res~: %lu us - percent: %.2f %%\n",
229 	       timediff, diff * 10 / 125, *percent);
230 
231 	return 0;
232 }
233 
amd_fam14h_start(void)234 static int amd_fam14h_start(void)
235 {
236 	int num, cpu;
237 	clock_gettime(CLOCK_REALTIME, &start_time);
238 	for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) {
239 		for (cpu = 0; cpu < cpu_count; cpu++)
240 			amd_fam14h_init(&amd_fam14h_cstates[num], cpu);
241 	}
242 #ifdef DEBUG
243 	clock_gettime(CLOCK_REALTIME, &dbg_time);
244 	dbg_timediff = timespec_diff_us(start_time, dbg_time);
245 	dprint("Enabling counters took: %lu us\n",
246 	       dbg_timediff);
247 #endif
248 	return 0;
249 }
250 
amd_fam14h_stop(void)251 static int amd_fam14h_stop(void)
252 {
253 	int num, cpu;
254 	struct timespec end_time;
255 
256 	clock_gettime(CLOCK_REALTIME, &end_time);
257 
258 	for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) {
259 		for (cpu = 0; cpu < cpu_count; cpu++)
260 			amd_fam14h_disable(&amd_fam14h_cstates[num], cpu);
261 	}
262 #ifdef DEBUG
263 	clock_gettime(CLOCK_REALTIME, &dbg_time);
264 	dbg_timediff = timespec_diff_us(end_time, dbg_time);
265 	dprint("Disabling counters took: %lu ns\n", dbg_timediff);
266 #endif
267 	timediff = timespec_diff_us(start_time, end_time);
268 	if (timediff / 1000 > OVERFLOW_MS)
269 		print_overflow_err((unsigned int)timediff / 1000000,
270 				   OVERFLOW_MS / 1000);
271 
272 	return 0;
273 }
274 
is_nbp1_capable(void)275 static int is_nbp1_capable(void)
276 {
277 	uint32_t val;
278 	val = pci_read_long(amd_fam14h_pci_dev, PCI_NBP1_CAP_OFFSET);
279 	return val & (1 << 31);
280 }
281 
amd_fam14h_register(void)282 struct cpuidle_monitor *amd_fam14h_register(void)
283 {
284 	int num;
285 
286 	if (cpupower_cpu_info.vendor != X86_VENDOR_AMD)
287 		return NULL;
288 
289 	if (cpupower_cpu_info.family == 0x14) {
290 		if (cpu_count <= 0 || cpu_count > 2) {
291 			fprintf(stderr, "AMD fam14h: Invalid cpu count: %d\n",
292 				cpu_count);
293 			return NULL;
294 		}
295 	} else
296 		return NULL;
297 
298 	/* We do not alloc for nbp1 machine wide counter */
299 	for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) {
300 		previous_count[num] = calloc(cpu_count,
301 					      sizeof(unsigned long long));
302 		current_count[num]  = calloc(cpu_count,
303 					      sizeof(unsigned long long));
304 	}
305 
306 	amd_fam14h_pci_dev = pci_acc_init(&pci_acc, pci_vendor_id, pci_dev_ids);
307 	if (amd_fam14h_pci_dev == NULL || pci_acc == NULL)
308 		return NULL;
309 
310 	if (!is_nbp1_capable())
311 		amd_fam14h_monitor.hw_states_num = AMD_FAM14H_STATE_NUM - 1;
312 
313 	amd_fam14h_monitor.name_len = strlen(amd_fam14h_monitor.name);
314 	return &amd_fam14h_monitor;
315 }
316 
amd_fam14h_unregister(void)317 static void amd_fam14h_unregister(void)
318 {
319 	int num;
320 	for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) {
321 		free(previous_count[num]);
322 		free(current_count[num]);
323 	}
324 	pci_cleanup(pci_acc);
325 }
326 
327 struct cpuidle_monitor amd_fam14h_monitor = {
328 	.name			= "Ontario",
329 	.hw_states		= amd_fam14h_cstates,
330 	.hw_states_num		= AMD_FAM14H_STATE_NUM,
331 	.start			= amd_fam14h_start,
332 	.stop			= amd_fam14h_stop,
333 	.do_register		= amd_fam14h_register,
334 	.unregister		= amd_fam14h_unregister,
335 	.needs_root		= 1,
336 	.overflow_s		= OVERFLOW_MS / 1000,
337 };
338 #endif /* #if defined(__i386__) || defined(__x86_64__) */
339