xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp (revision 922e7ba1f3228e6f28db69b0ded8f86eb32dea17)
1 /*
2  * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Axel Dörfler, axeld@pinc-software.de
7  */
8 
9 
10 #include "intel_extreme.h"
11 
12 #include "AreaKeeper.h"
13 #include "driver.h"
14 #include "utility.h"
15 
16 #include <unistd.h>
17 #include <stdio.h>
18 #include <string.h>
19 #include <errno.h>
20 
21 #include <driver_settings.h>
22 #include <util/kernel_cpp.h>
23 
24 
25 #define TRACE_DEVICE
26 #ifdef TRACE_DEVICE
27 #	define TRACE(x) dprintf x
28 #else
29 #	define TRACE(x) ;
30 #endif
31 
32 
33 static void
34 init_overlay_registers(overlay_registers *registers)
35 {
36 	memset(registers, 0, B_PAGE_SIZE);
37 
38 	registers->contrast_correction = 0x48;
39 	registers->saturation_cos_correction = 0x9a;
40 		// this by-passes contrast and saturation correction
41 }
42 
43 
44 static void
45 read_settings(bool &hardwareCursor)
46 {
47 	hardwareCursor = false;
48 
49 	void *settings = load_driver_settings("intel_extreme");
50 	if (settings != NULL) {
51 		hardwareCursor = get_driver_boolean_parameter(settings,
52 			"hardware_cursor", true, true);
53 
54 		unload_driver_settings(settings);
55 	}
56 }
57 
58 
59 static int32
60 release_vblank_sem(intel_info &info)
61 {
62 	int32 count;
63 	if (get_sem_count(info.shared_info->vblank_sem, &count) == B_OK
64 		&& count < 0) {
65 		release_sem_etc(info.shared_info->vblank_sem, -count,
66 			B_DO_NOT_RESCHEDULE);
67 		return B_INVOKE_SCHEDULER;
68 	}
69 
70 	return B_HANDLED_INTERRUPT;
71 }
72 
73 
74 static int32
75 intel_interrupt_handler(void *data)
76 {
77 	intel_info &info = *(intel_info *)data;
78 
79 	uint32 identity = read16(info.registers + INTEL_INTERRUPT_IDENTITY);
80 	if (identity == 0)
81 		return B_UNHANDLED_INTERRUPT;
82 
83 	int32 handled = B_HANDLED_INTERRUPT;
84 
85 	if ((identity & INTERRUPT_VBLANK_PIPEA) != 0) {
86 		handled = release_vblank_sem(info);
87 
88 		// make sure we'll get another one of those
89 		write32(info.registers + INTEL_DISPLAY_A_PIPE_STATUS,
90 			DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
91 	}
92 
93 	if ((identity & INTERRUPT_VBLANK_PIPEB) != 0) {
94 		handled = release_vblank_sem(info);
95 
96 		// make sure we'll get another one of those
97 		write32(info.registers + INTEL_DISPLAY_B_PIPE_STATUS,
98 			DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
99 	}
100 
101 	// setting the bit clears it!
102 	write16(info.registers + INTEL_INTERRUPT_IDENTITY, identity);
103 
104 	return handled;
105 }
106 
107 
108 static void
109 init_interrupt_handler(intel_info &info)
110 {
111 	info.shared_info->vblank_sem = create_sem(0, "intel extreme vblank");
112 	if (info.shared_info->vblank_sem < B_OK)
113 		return;
114 
115 	status_t status = B_OK;
116 
117 	// We need to change the owner of the sem to the calling team (usually the
118 	// app_server), because userland apps cannot acquire kernel semaphores
119 	thread_id thread = find_thread(NULL);
120 	thread_info threadInfo;
121 	if (get_thread_info(thread, &threadInfo) != B_OK
122 		|| set_sem_owner(info.shared_info->vblank_sem, threadInfo.team)
123 			!= B_OK) {
124 		status = B_ERROR;
125 	}
126 
127 	if (status == B_OK && info.pci->u.h0.interrupt_pin != 0x00
128 		&& info.pci->u.h0.interrupt_line != 0xff) {
129 		// we've gotten an interrupt line for us to use
130 
131 		info.fake_interrupts = false;
132 
133 		status = install_io_interrupt_handler(info.pci->u.h0.interrupt_line,
134 			&intel_interrupt_handler, (void *)&info, 0);
135 		if (status == B_OK) {
136 			write32(info.registers + INTEL_DISPLAY_A_PIPE_STATUS,
137 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
138 			write32(info.registers + INTEL_DISPLAY_B_PIPE_STATUS,
139 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
140 			write16(info.registers + INTEL_INTERRUPT_IDENTITY, ~0);
141 
142 			// enable interrupts - we only want VBLANK interrupts
143 			write16(info.registers + INTEL_INTERRUPT_ENABLED,
144 				read16(info.registers + INTEL_INTERRUPT_ENABLED)
145 				| INTERRUPT_VBLANK_PIPEA | INTERRUPT_VBLANK_PIPEB);
146 			write16(info.registers + INTEL_INTERRUPT_MASK,
147 				~(INTERRUPT_VBLANK_PIPEA | INTERRUPT_VBLANK_PIPEB));
148 		}
149 	}
150 	if (status < B_OK) {
151 		// There is no interrupt reserved for us, or we couldn't install our
152 		// interrupt handler, let's fake the vblank interrupt for our clients
153 		// using a timer interrupt
154 		info.fake_interrupts = true;
155 
156 		// TODO: fake interrupts!
157 		TRACE((DEVICE_NAME "Fake interrupt mode (no PCI interrupt line assigned)"));
158 		status = B_ERROR;
159 	}
160 
161 	if (status < B_OK) {
162 		delete_sem(info.shared_info->vblank_sem);
163 		info.shared_info->vblank_sem = B_ERROR;
164 	}
165 }
166 
167 
168 //	#pragma mark -
169 
170 
171 status_t
172 intel_free_memory(intel_info &info, addr_t base)
173 {
174 	return gGART->free_memory(info.aperture, base);
175 }
176 
177 
178 status_t
179 intel_allocate_memory(intel_info &info, size_t size, size_t alignment,
180 	uint32 flags, addr_t *_base, phys_addr_t *_physicalBase)
181 {
182 	return gGART->allocate_memory(info.aperture, size, alignment,
183 		flags, _base, _physicalBase);
184 }
185 
186 
187 status_t
188 intel_extreme_init(intel_info &info)
189 {
190 	info.aperture = gGART->map_aperture(info.pci->bus, info.pci->device,
191 		info.pci->function, 0, &info.aperture_base);
192 	if (info.aperture < B_OK)
193 		return info.aperture;
194 
195 	AreaKeeper sharedCreator;
196 	info.shared_area = sharedCreator.Create("intel extreme shared info",
197 		(void **)&info.shared_info, B_ANY_KERNEL_ADDRESS,
198 		ROUND_TO_PAGE_SIZE(sizeof(intel_shared_info)) + 3 * B_PAGE_SIZE,
199 		B_FULL_LOCK, 0);
200 	if (info.shared_area < B_OK) {
201 		gGART->unmap_aperture(info.aperture);
202 		return info.shared_area;
203 	}
204 
205 	memset((void *)info.shared_info, 0, sizeof(intel_shared_info));
206 
207 	int fbIndex = 0;
208 	int mmioIndex = 1;
209 	if (info.device_type.InFamily(INTEL_TYPE_9xx)) {
210 		// For some reason Intel saw the need to change the order of the
211 		// mappings with the introduction of the i9xx family
212 		mmioIndex = 0;
213 		fbIndex = 2;
214 	}
215 
216 	// evaluate driver settings, if any
217 
218 	bool hardwareCursor;
219 	read_settings(hardwareCursor);
220 
221 	// memory mapped I/O
222 
223 	// TODO: registers are mapped twice (by us and intel_gart), maybe we
224 	// can share it between the drivers
225 
226 	AreaKeeper mmioMapper;
227 	info.registers_area = mmioMapper.Map("intel extreme mmio",
228 		(void *)info.pci->u.h0.base_registers[mmioIndex],
229 		info.pci->u.h0.base_register_sizes[mmioIndex],
230 		B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
231 		(void **)&info.registers);
232 	if (mmioMapper.InitCheck() < B_OK) {
233 		dprintf(DEVICE_NAME ": could not map memory I/O!\n");
234 		gGART->unmap_aperture(info.aperture);
235 		return info.registers_area;
236 	}
237 
238 	// make sure bus master, memory-mapped I/O, and frame buffer is enabled
239 	set_pci_config(info.pci, PCI_command, 2, get_pci_config(info.pci, PCI_command, 2)
240 		| PCI_command_io | PCI_command_memory | PCI_command_master);
241 
242 	// reserve ring buffer memory (currently, this memory is placed in
243 	// the graphics memory), but this could bring us problems with
244 	// write combining...
245 
246 	ring_buffer &primary = info.shared_info->primary_ring_buffer;
247 	if (intel_allocate_memory(info, 16 * B_PAGE_SIZE, 0, 0,
248 			(addr_t *)&primary.base) == B_OK) {
249 		primary.register_base = INTEL_PRIMARY_RING_BUFFER;
250 		primary.size = 16 * B_PAGE_SIZE;
251 		primary.offset = (addr_t)primary.base - info.aperture_base;
252 	}
253 
254 	// Clock gating
255 	// Fix some problems on certain chips (taken from X driver)
256 	// TODO: clean this up
257 	if (info.pci->device_id == 0x2a02 || info.pci->device_id == 0x2a12) {
258 		dprintf("i965GM/i965GME quirk\n");
259 		write32(info.registers + 0x6204, (1L << 29));
260 	} else if (info.device_type.InGroup(INTEL_TYPE_G4x)) {
261 		dprintf("G4x clock gating\n");
262 		write32(info.registers + 0x6204, 0);
263 		write32(info.registers + 0x6208, (1L << 9) | (1L << 7) | (1L << 6));
264 		write32(info.registers + 0x6210, 0);
265 
266 		uint32 gateValue = (1L << 28) | (1L << 3) | (1L << 2);
267 		if ((info.device_type.type & INTEL_TYPE_MOBILE) == INTEL_TYPE_MOBILE) {
268 			dprintf("G4x mobile clock gating\n");
269 		    gateValue |= 1L << 18;
270 		}
271 		write32(info.registers + 0x6200, gateValue);
272 	} else {
273 		dprintf("i965 quirk\n");
274 		write32(info.registers + 0x6204, (1L << 29) | (1L << 23));
275 	}
276 	write32(info.registers + 0x7408, 0x10);
277 
278 	// no errors, so keep areas and mappings
279 	sharedCreator.Detach();
280 	mmioMapper.Detach();
281 
282 	aperture_info apertureInfo;
283 	gGART->get_aperture_info(info.aperture, &apertureInfo);
284 
285 	info.shared_info->registers_area = info.registers_area;
286 	info.shared_info->graphics_memory = (uint8 *)info.aperture_base;
287 	info.shared_info->physical_graphics_memory = apertureInfo.physical_base;
288 	info.shared_info->graphics_memory_size = apertureInfo.size;
289 	info.shared_info->frame_buffer = 0;
290 	info.shared_info->dpms_mode = B_DPMS_ON;
291 
292 	if (info.device_type.InFamily(INTEL_TYPE_9xx)) {
293 		info.shared_info->pll_info.reference_frequency = 96000;	// 96 kHz
294 		info.shared_info->pll_info.max_frequency = 400000;
295 			// 400 MHz RAM DAC speed
296 		info.shared_info->pll_info.min_frequency = 20000;		// 20 MHz
297 	} else {
298 		info.shared_info->pll_info.reference_frequency = 48000;	// 48 kHz
299 		info.shared_info->pll_info.max_frequency = 350000;
300 			// 350 MHz RAM DAC speed
301 		info.shared_info->pll_info.min_frequency = 25000;		// 25 MHz
302 	}
303 
304 	info.shared_info->pll_info.divisor_register = INTEL_DISPLAY_A_PLL_DIVISOR_0;
305 
306 	info.shared_info->device_type = info.device_type;
307 #ifdef __HAIKU__
308 	strlcpy(info.shared_info->device_identifier, info.device_identifier,
309 		sizeof(info.shared_info->device_identifier));
310 #else
311 	strcpy(info.shared_info->device_identifier, info.device_identifier);
312 #endif
313 
314 	// setup overlay registers
315 
316 	if (intel_allocate_memory(info, B_PAGE_SIZE, 0,
317 			intel_uses_physical_overlay(*info.shared_info)
318 				? B_APERTURE_NEED_PHYSICAL : 0,
319 			(addr_t *)&info.overlay_registers,
320 			&info.shared_info->physical_overlay_registers) == B_OK) {
321 		info.shared_info->overlay_offset = (addr_t)info.overlay_registers
322 			- info.aperture_base;
323 	}
324 
325 	init_overlay_registers(info.overlay_registers);
326 
327 	// Allocate hardware status page and the cursor memory
328 
329 	if (intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
330 			(addr_t *)info.shared_info->status_page,
331 			&info.shared_info->physical_status_page) == B_OK) {
332 		// TODO: set status page
333 	}
334 	if (hardwareCursor) {
335 		intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
336 			(addr_t *)&info.shared_info->cursor_memory,
337 			&info.shared_info->physical_cursor_memory);
338 	}
339 
340 	init_interrupt_handler(info);
341 
342 	TRACE((DEVICE_NAME "_init() completed successfully!\n"));
343 	return B_OK;
344 }
345 
346 
347 void
348 intel_extreme_uninit(intel_info &info)
349 {
350 	TRACE((DEVICE_NAME": intel_extreme_uninit()\n"));
351 
352 	if (!info.fake_interrupts && info.shared_info->vblank_sem > 0) {
353 		// disable interrupt generation
354 		write16(info.registers + INTEL_INTERRUPT_ENABLED, 0);
355 		write16(info.registers + INTEL_INTERRUPT_MASK, ~0);
356 
357 		remove_io_interrupt_handler(info.pci->u.h0.interrupt_line,
358 			intel_interrupt_handler, &info);
359 	}
360 
361 	gGART->unmap_aperture(info.aperture);
362 
363 	delete_area(info.registers_area);
364 	delete_area(info.shared_area);
365 }
366 
367