xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/intel_extreme.cpp (revision 9e15c9f153c5ffff9ad51b95b581326eb579b0fd)
1 /*
2  * Copyright 2006-2018, 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  *		Alexander von Gluck IV, kallisti5@unixzen.com
8  *		Adrien Destugues, pulkomandy@pulkomandy.tk
9  */
10 
11 
12 #include "intel_extreme.h"
13 
14 #include "AreaKeeper.h"
15 #include <unistd.h>
16 #include <stdio.h>
17 #include <string.h>
18 #include <errno.h>
19 
20 #include <boot_item.h>
21 #include <driver_settings.h>
22 #include <util/kernel_cpp.h>
23 
24 #include <vesa_info.h>
25 
26 #include "driver.h"
27 #include "power.h"
28 #include "utility.h"
29 
30 
31 #define TRACE_INTELEXTREME
32 #ifdef TRACE_INTELEXTREME
33 #	define TRACE(x...) dprintf("intel_extreme: " x)
34 #else
35 #	define TRACE(x) ;
36 #endif
37 
38 #define ERROR(x...) dprintf("intel_extreme: " x)
39 #define CALLED(x...) TRACE("intel_extreme: CALLED %s\n", __PRETTY_FUNCTION__)
40 
41 
42 static void
43 init_overlay_registers(overlay_registers* _registers)
44 {
45 	user_memset(_registers, 0, B_PAGE_SIZE);
46 
47 	overlay_registers registers;
48 	memset(&registers, 0, sizeof(registers));
49 	registers.contrast_correction = 0x48;
50 	registers.saturation_cos_correction = 0x9a;
51 		// this by-passes contrast and saturation correction
52 
53 	user_memcpy(_registers, &registers, sizeof(overlay_registers));
54 }
55 
56 
57 static void
58 read_settings(bool &hardwareCursor)
59 {
60 	hardwareCursor = false;
61 
62 	void* settings = load_driver_settings("intel_extreme");
63 	if (settings != NULL) {
64 		hardwareCursor = get_driver_boolean_parameter(settings,
65 			"hardware_cursor", true, true);
66 
67 		unload_driver_settings(settings);
68 	}
69 }
70 
71 
72 static int32
73 release_vblank_sem(intel_info &info)
74 {
75 	int32 count;
76 	if (get_sem_count(info.shared_info->vblank_sem, &count) == B_OK
77 		&& count < 0) {
78 		release_sem_etc(info.shared_info->vblank_sem, -count,
79 			B_DO_NOT_RESCHEDULE);
80 		return B_INVOKE_SCHEDULER;
81 	}
82 
83 	return B_HANDLED_INTERRUPT;
84 }
85 
86 
87 /** Get the appropriate interrupt mask for enabling or testing interrupts on
88  * the given pipes.
89  *
90  * The bits to test or set are different depending on the hardware generation.
91  *
92  * \param info Intel_extreme driver information
93  * \param pipes bit mask of the pipes to use
94  * \param enable true to get the mask for enabling the interrupts, false to get
95  *               the mask for testing them.
96  */
97 static uint32
98 intel_get_interrupt_mask(intel_info& info, int pipes, bool enable)
99 {
100 	uint32 mask = 0;
101 	bool hasPCH = info.pch_info != INTEL_PCH_NONE;
102 
103 	// Intel changed the PCH register mapping between Sandy Bridge and the
104 	// later generations (Ivy Bridge and up).
105 	// The PCH register itself does not exist in pre-PCH platforms, and the
106 	// previous interrupt register of course also had a different mapping.
107 
108 	if ((pipes & INTEL_PIPE_A) != 0) {
109 		if (info.device_type.InGroup(INTEL_GROUP_SNB)
110 				|| info.device_type.InGroup(INTEL_GROUP_ILK))
111 			mask |= PCH_INTERRUPT_VBLANK_PIPEA_SNB;
112 		else if (hasPCH)
113 			mask |= PCH_INTERRUPT_VBLANK_PIPEA;
114 		else
115 			mask |= INTERRUPT_VBLANK_PIPEA;
116 	}
117 
118 	if ((pipes & INTEL_PIPE_B) != 0) {
119 		if (info.device_type.InGroup(INTEL_GROUP_SNB)
120 				|| info.device_type.InGroup(INTEL_GROUP_ILK))
121 			mask |= PCH_INTERRUPT_VBLANK_PIPEB_SNB;
122 		else if (hasPCH)
123 			mask |= PCH_INTERRUPT_VBLANK_PIPEB;
124 		else
125 			mask |= INTERRUPT_VBLANK_PIPEB;
126 	}
127 
128 #if 0 // FIXME enable when we support the 3rd pipe
129 	if ((pipes & INTEL_PIPE_C) != 0) {
130 		// Older generations only had two pipes
131 		if (hasPCH && info.device_type.Generation() > 6)
132 			mask |= PCH_INTERRUPT_VBLANK_PIPEC;
133 	}
134 #endif
135 
136 	// On SandyBridge, there is an extra "global enable" flag, which must also
137 	// be set when enabling the interrupts (but not when testing for them).
138 	if (enable && info.device_type.InFamily(INTEL_FAMILY_SER5))
139 		mask |= PCH_INTERRUPT_GLOBAL_SNB;
140 
141 	return mask;
142 }
143 
144 
145 static int32
146 intel_interrupt_handler(void* data)
147 {
148 	intel_info &info = *(intel_info*)data;
149 	uint32 reg = find_reg(info, INTEL_INTERRUPT_IDENTITY);
150 	bool hasPCH = (info.pch_info != INTEL_PCH_NONE);
151 	uint32 identity;
152 
153 	if (hasPCH)
154 		identity = read32(info, reg);
155 	else
156 		identity = read16(info, reg);
157 
158 	if (identity == 0)
159 		return B_UNHANDLED_INTERRUPT;
160 
161 	int32 handled = B_HANDLED_INTERRUPT;
162 
163 	while (identity != 0) {
164 
165 		uint32 mask = intel_get_interrupt_mask(info, INTEL_PIPE_A, false);
166 
167 		if ((identity & mask) != 0) {
168 			handled = release_vblank_sem(info);
169 
170 			// make sure we'll get another one of those
171 			write32(info, INTEL_DISPLAY_A_PIPE_STATUS,
172 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
173 		}
174 
175 		mask = intel_get_interrupt_mask(info, INTEL_PIPE_B, false);
176 		if ((identity & mask) != 0) {
177 			handled = release_vblank_sem(info);
178 
179 			// make sure we'll get another one of those
180 			write32(info, INTEL_DISPLAY_B_PIPE_STATUS,
181 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
182 		}
183 
184 #if 0
185 		// FIXME we don't have supprot for the 3rd pipe yet
186 		mask = hasPCH ? PCH_INTERRUPT_VBLANK_PIPEC
187 			: 0;
188 		if ((identity & mask) != 0) {
189 			handled = release_vblank_sem(info);
190 
191 			// make sure we'll get another one of those
192 			write32(info, INTEL_DISPLAY_C_PIPE_STATUS,
193 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
194 		}
195 #endif
196 
197 		// setting the bit clears it!
198 		if (hasPCH) {
199 			write32(info, reg, identity);
200 			identity = read32(info, reg);
201 		} else {
202 			write16(info, reg, identity);
203 			identity = read16(info, reg);
204 		}
205 	}
206 
207 	return handled;
208 }
209 
210 
211 static void
212 init_interrupt_handler(intel_info &info)
213 {
214 	info.shared_info->vblank_sem = create_sem(0, "intel extreme vblank");
215 	if (info.shared_info->vblank_sem < B_OK)
216 		return;
217 
218 	status_t status = B_OK;
219 
220 	// We need to change the owner of the sem to the calling team (usually the
221 	// app_server), because userland apps cannot acquire kernel semaphores
222 	thread_id thread = find_thread(NULL);
223 	thread_info threadInfo;
224 	if (get_thread_info(thread, &threadInfo) != B_OK
225 		|| set_sem_owner(info.shared_info->vblank_sem, threadInfo.team)
226 			!= B_OK) {
227 		status = B_ERROR;
228 	}
229 
230 	// Find the right interrupt vector, using MSIs if available.
231 	info.irq = 0xff;
232 	info.use_msi = false;
233 	if (info.pci->u.h0.interrupt_pin != 0x00)
234 		info.irq = info.pci->u.h0.interrupt_line;
235 	if (gPCIx86Module != NULL && gPCIx86Module->get_msi_count(info.pci->bus,
236 			info.pci->device, info.pci->function) >= 1) {
237 		uint8 msiVector = 0;
238 		if (gPCIx86Module->configure_msi(info.pci->bus, info.pci->device,
239 				info.pci->function, 1, &msiVector) == B_OK
240 			&& gPCIx86Module->enable_msi(info.pci->bus, info.pci->device,
241 				info.pci->function) == B_OK) {
242 			ERROR("using message signaled interrupts\n");
243 			info.irq = msiVector;
244 			info.use_msi = true;
245 		}
246 	}
247 
248 	if (status == B_OK && info.irq != 0xff) {
249 		// we've gotten an interrupt line for us to use
250 
251 		info.fake_interrupts = false;
252 
253 		status = install_io_interrupt_handler(info.irq,
254 			&intel_interrupt_handler, (void*)&info, 0);
255 		if (status == B_OK) {
256 			write32(info, INTEL_DISPLAY_A_PIPE_STATUS,
257 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
258 			write32(info, INTEL_DISPLAY_B_PIPE_STATUS,
259 				DISPLAY_PIPE_VBLANK_STATUS | DISPLAY_PIPE_VBLANK_ENABLED);
260 
261 			bool hasPCH = (info.pch_info != INTEL_PCH_NONE);
262 
263 			uint32 enable = intel_get_interrupt_mask(info,
264 				INTEL_PIPE_A | INTEL_PIPE_B, true);
265 
266 			if (hasPCH) {
267 				// Clear all the interrupts
268 				write32(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), ~0);
269 
270 				// enable interrupts - we only want VBLANK interrupts
271 				write32(info, find_reg(info, INTEL_INTERRUPT_ENABLED), enable);
272 				write32(info, find_reg(info, INTEL_INTERRUPT_MASK), ~enable);
273 			} else {
274 				// Clear all the interrupts
275 				write16(info, find_reg(info, INTEL_INTERRUPT_IDENTITY), ~0);
276 
277 				// enable interrupts - we only want VBLANK interrupts
278 				write16(info, find_reg(info, INTEL_INTERRUPT_ENABLED), enable);
279 				write16(info, find_reg(info, INTEL_INTERRUPT_MASK), ~enable);
280 			}
281 
282 		}
283 	}
284 	if (status < B_OK) {
285 		// There is no interrupt reserved for us, or we couldn't install our
286 		// interrupt handler, let's fake the vblank interrupt for our clients
287 		// using a timer interrupt
288 		info.fake_interrupts = true;
289 
290 		// TODO: fake interrupts!
291 		TRACE("Fake interrupt mode (no PCI interrupt line assigned\n");
292 		status = B_ERROR;
293 	}
294 
295 	if (status < B_OK) {
296 		delete_sem(info.shared_info->vblank_sem);
297 		info.shared_info->vblank_sem = B_ERROR;
298 	}
299 }
300 
301 
302 //	#pragma mark -
303 
304 
305 status_t
306 intel_free_memory(intel_info &info, addr_t base)
307 {
308 	return gGART->free_memory(info.aperture, base);
309 }
310 
311 
312 status_t
313 intel_allocate_memory(intel_info &info, size_t size, size_t alignment,
314 	uint32 flags, addr_t* _base, phys_addr_t* _physicalBase)
315 {
316 	return gGART->allocate_memory(info.aperture, size, alignment,
317 		flags, _base, _physicalBase);
318 }
319 
320 
321 status_t
322 intel_extreme_init(intel_info &info)
323 {
324 	CALLED();
325 	info.aperture = gGART->map_aperture(info.pci->bus, info.pci->device,
326 		info.pci->function, 0, &info.aperture_base);
327 	if (info.aperture < B_OK) {
328 		ERROR("error: could not map GART aperture! (%s)\n",
329 			strerror(info.aperture));
330 		return info.aperture;
331 	}
332 
333 	AreaKeeper sharedCreator;
334 	info.shared_area = sharedCreator.Create("intel extreme shared info",
335 		(void**)&info.shared_info, B_ANY_KERNEL_ADDRESS,
336 		ROUND_TO_PAGE_SIZE(sizeof(intel_shared_info)) + 3 * B_PAGE_SIZE,
337 		B_FULL_LOCK,
338 		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA);
339 	if (info.shared_area < B_OK) {
340 		ERROR("error: could not create shared area!\n");
341 		gGART->unmap_aperture(info.aperture);
342 		return info.shared_area;
343 	}
344 
345 	memset((void*)info.shared_info, 0, sizeof(intel_shared_info));
346 
347 	int mmioIndex = 1;
348 	if (info.device_type.Generation() >= 3) {
349 		// For some reason Intel saw the need to change the order of the
350 		// mappings with the introduction of the i9xx family
351 		mmioIndex = 0;
352 	}
353 
354 	// evaluate driver settings, if any
355 
356 	bool hardwareCursor;
357 	read_settings(hardwareCursor);
358 
359 	// memory mapped I/O
360 
361 	// TODO: registers are mapped twice (by us and intel_gart), maybe we
362 	// can share it between the drivers
363 
364 	AreaKeeper mmioMapper;
365 	info.registers_area = mmioMapper.Map("intel extreme mmio",
366 		info.pci->u.h0.base_registers[mmioIndex],
367 		info.pci->u.h0.base_register_sizes[mmioIndex],
368 		B_ANY_KERNEL_ADDRESS,
369 		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA | B_CLONEABLE_AREA,
370 		(void**)&info.registers);
371 	if (mmioMapper.InitCheck() < B_OK) {
372 		ERROR("error: could not map memory I/O!\n");
373 		gGART->unmap_aperture(info.aperture);
374 		return info.registers_area;
375 	}
376 
377 	bool hasPCH = (info.pch_info != INTEL_PCH_NONE);
378 
379 	ERROR("Init Intel generation %d GPU %s PCH split.\n",
380 		info.device_type.Generation(), hasPCH ? "with" : "without");
381 
382 	uint32* blocks = info.shared_info->register_blocks;
383 	blocks[REGISTER_BLOCK(REGS_FLAT)] = 0;
384 
385 	// setup the register blocks for the different architectures
386 	if (hasPCH) {
387 		// PCH based platforms (IronLake through ultra-low-power Broadwells)
388 		blocks[REGISTER_BLOCK(REGS_NORTH_SHARED)]
389 			= PCH_NORTH_SHARED_REGISTER_BASE;
390 		blocks[REGISTER_BLOCK(REGS_NORTH_PIPE_AND_PORT)]
391 			= PCH_NORTH_PIPE_AND_PORT_REGISTER_BASE;
392 		blocks[REGISTER_BLOCK(REGS_NORTH_PLANE_CONTROL)]
393 			= PCH_NORTH_PLANE_CONTROL_REGISTER_BASE;
394 		blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)]
395 			= PCH_SOUTH_SHARED_REGISTER_BASE;
396 		blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)]
397 			= PCH_SOUTH_TRANSCODER_AND_PORT_REGISTER_BASE;
398 	} else {
399 		// (G)MCH/ICH based platforms
400 		blocks[REGISTER_BLOCK(REGS_NORTH_SHARED)]
401 			= MCH_SHARED_REGISTER_BASE;
402 		blocks[REGISTER_BLOCK(REGS_NORTH_PIPE_AND_PORT)]
403 			= MCH_PIPE_AND_PORT_REGISTER_BASE;
404 		blocks[REGISTER_BLOCK(REGS_NORTH_PLANE_CONTROL)]
405 			= MCH_PLANE_CONTROL_REGISTER_BASE;
406 		blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)]
407 			= ICH_SHARED_REGISTER_BASE;
408 		blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)]
409 			= ICH_PORT_REGISTER_BASE;
410 	}
411 
412 	// Everything in the display PRM gets +0x180000
413 	if (info.device_type.InGroup(INTEL_GROUP_VLV)) {
414 		// "I nearly got violent with the hw guys when they told me..."
415 		blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)] += VLV_DISPLAY_BASE;
416 		blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)] += VLV_DISPLAY_BASE;
417 	}
418 
419 	TRACE("REGS_NORTH_SHARED: 0x%" B_PRIx32 "\n",
420 		blocks[REGISTER_BLOCK(REGS_NORTH_SHARED)]);
421 	TRACE("REGS_NORTH_PIPE_AND_PORT: 0x%" B_PRIx32 "\n",
422 		blocks[REGISTER_BLOCK(REGS_NORTH_PIPE_AND_PORT)]);
423 	TRACE("REGS_NORTH_PLANE_CONTROL: 0x%" B_PRIx32 "\n",
424 		blocks[REGISTER_BLOCK(REGS_NORTH_PLANE_CONTROL)]);
425 	TRACE("REGS_SOUTH_SHARED: 0x%" B_PRIx32 "\n",
426 		blocks[REGISTER_BLOCK(REGS_SOUTH_SHARED)]);
427 	TRACE("REGS_SOUTH_TRANSCODER_PORT: 0x%" B_PRIx32 "\n",
428 		blocks[REGISTER_BLOCK(REGS_SOUTH_TRANSCODER_PORT)]);
429 
430 	// make sure bus master, memory-mapped I/O, and frame buffer is enabled
431 	set_pci_config(info.pci, PCI_command, 2, get_pci_config(info.pci,
432 		PCI_command, 2) | PCI_command_io | PCI_command_memory
433 		| PCI_command_master);
434 
435 	// reserve ring buffer memory (currently, this memory is placed in
436 	// the graphics memory), but this could bring us problems with
437 	// write combining...
438 
439 	ring_buffer &primary = info.shared_info->primary_ring_buffer;
440 	if (intel_allocate_memory(info, 16 * B_PAGE_SIZE, 0, 0,
441 			(addr_t*)&primary.base) == B_OK) {
442 		primary.register_base = INTEL_PRIMARY_RING_BUFFER;
443 		primary.size = 16 * B_PAGE_SIZE;
444 		primary.offset = (addr_t)primary.base - info.aperture_base;
445 	}
446 
447 	// Enable clock gating
448 	intel_en_gating(info);
449 
450 	// Enable automatic gpu downclocking if we can to save power
451 	intel_en_downclock(info);
452 
453 	// no errors, so keep areas and mappings
454 	sharedCreator.Detach();
455 	mmioMapper.Detach();
456 
457 	aperture_info apertureInfo;
458 	gGART->get_aperture_info(info.aperture, &apertureInfo);
459 
460 	info.shared_info->registers_area = info.registers_area;
461 	info.shared_info->graphics_memory = (uint8*)info.aperture_base;
462 	info.shared_info->physical_graphics_memory = apertureInfo.physical_base;
463 	info.shared_info->graphics_memory_size = apertureInfo.size;
464 	info.shared_info->frame_buffer = 0;
465 	info.shared_info->dpms_mode = B_DPMS_ON;
466 
467 	// Pull VBIOS panel mode for later use
468 	info.shared_info->got_vbt = get_lvds_mode_from_bios(
469 		&info.shared_info->panel_mode);
470 
471 	/* at least 855gm can't drive more than one head at time */
472 	if (info.device_type.InFamily(INTEL_FAMILY_8xx))
473 		info.shared_info->single_head_locked = 1;
474 
475 	if (info.device_type.InFamily(INTEL_FAMILY_SER5)) {
476 		info.shared_info->pll_info.reference_frequency = 120000;	// 120 MHz
477 		info.shared_info->pll_info.max_frequency = 350000;
478 			// 350 MHz RAM DAC speed
479 		info.shared_info->pll_info.min_frequency = 20000;		// 20 MHz
480 	} else if (info.device_type.InFamily(INTEL_FAMILY_9xx)) {
481 		info.shared_info->pll_info.reference_frequency = 96000;	// 96 MHz
482 		info.shared_info->pll_info.max_frequency = 400000;
483 			// 400 MHz RAM DAC speed
484 		info.shared_info->pll_info.min_frequency = 20000;		// 20 MHz
485 	} else {
486 		info.shared_info->pll_info.reference_frequency = 48000;	// 48 MHz
487 		info.shared_info->pll_info.max_frequency = 350000;
488 			// 350 MHz RAM DAC speed
489 		info.shared_info->pll_info.min_frequency = 25000;		// 25 MHz
490 	}
491 
492 	info.shared_info->pll_info.divisor_register = INTEL_DISPLAY_A_PLL_DIVISOR_0;
493 
494 	info.shared_info->pch_info = info.pch_info;
495 
496 	info.shared_info->device_type = info.device_type;
497 #ifdef __HAIKU__
498 	strlcpy(info.shared_info->device_identifier, info.device_identifier,
499 		sizeof(info.shared_info->device_identifier));
500 #else
501 	strcpy(info.shared_info->device_identifier, info.device_identifier);
502 #endif
503 
504 	// setup overlay registers
505 
506 	status_t status = intel_allocate_memory(info, B_PAGE_SIZE, 0,
507 		intel_uses_physical_overlay(*info.shared_info)
508 				? B_APERTURE_NEED_PHYSICAL : 0,
509 		(addr_t*)&info.overlay_registers,
510 		&info.shared_info->physical_overlay_registers);
511 	if (status == B_OK) {
512 		info.shared_info->overlay_offset = (addr_t)info.overlay_registers
513 			- info.aperture_base;
514 		TRACE("Overlay registers mapped at 0x%" B_PRIx32 " = %p - %"
515 			B_PRIxADDR " (%" B_PRIxPHYSADDR ")\n",
516 			info.shared_info->overlay_offset, info.overlay_registers,
517 			info.aperture_base, info.shared_info->physical_overlay_registers);
518 		init_overlay_registers(info.overlay_registers);
519 	} else {
520 		ERROR("error: could not allocate overlay memory! %s\n",
521 			strerror(status));
522 	}
523 
524 	// Allocate hardware status page and the cursor memory
525 	TRACE("Allocating hardware status page");
526 
527 	if (intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
528 			(addr_t*)info.shared_info->status_page,
529 			&info.shared_info->physical_status_page) == B_OK) {
530 		// TODO: set status page
531 	}
532 	if (hardwareCursor) {
533 		intel_allocate_memory(info, B_PAGE_SIZE, 0, B_APERTURE_NEED_PHYSICAL,
534 			(addr_t*)&info.shared_info->cursor_memory,
535 			&info.shared_info->physical_cursor_memory);
536 	}
537 
538 	edid1_info* edidInfo = (edid1_info*)get_boot_item(VESA_EDID_BOOT_INFO,
539 		NULL);
540 	if (edidInfo != NULL) {
541 		info.shared_info->has_vesa_edid_info = true;
542 		memcpy(&info.shared_info->vesa_edid_info, edidInfo, sizeof(edid1_info));
543 	}
544 
545 	init_interrupt_handler(info);
546 
547 	if (hasPCH) {
548 		if (info.device_type.Generation() == 5) {
549 			info.shared_info->fdi_link_frequency = (read32(info, FDI_PLL_BIOS_0)
550 				& FDI_PLL_FB_CLOCK_MASK) + 2;
551 			info.shared_info->fdi_link_frequency *= 100;
552 		} else {
553 			info.shared_info->fdi_link_frequency = 2700;
554 		}
555 	} else {
556 		info.shared_info->fdi_link_frequency = 0;
557 	}
558 
559 	TRACE("%s: completed successfully!\n", __func__);
560 	return B_OK;
561 }
562 
563 
564 void
565 intel_extreme_uninit(intel_info &info)
566 {
567 	CALLED();
568 
569 	if (!info.fake_interrupts && info.shared_info->vblank_sem > 0) {
570 		bool hasPCH = (info.pch_info != INTEL_PCH_NONE);
571 
572 		// disable interrupt generation
573 		if (hasPCH) {
574 			write32(info, find_reg(info, INTEL_INTERRUPT_ENABLED), 0);
575 			write32(info, find_reg(info, INTEL_INTERRUPT_MASK), ~0);
576 		} else {
577 			write16(info, find_reg(info, INTEL_INTERRUPT_ENABLED), 0);
578 			write16(info, find_reg(info, INTEL_INTERRUPT_MASK), ~0);
579 		}
580 
581 		remove_io_interrupt_handler(info.irq, intel_interrupt_handler, &info);
582 
583 		if (info.use_msi && gPCIx86Module != NULL) {
584 			gPCIx86Module->disable_msi(info.pci->bus,
585 				info.pci->device, info.pci->function);
586 			gPCIx86Module->unconfigure_msi(info.pci->bus,
587 				info.pci->device, info.pci->function);
588 		}
589 	}
590 
591 	gGART->unmap_aperture(info.aperture);
592 
593 	delete_area(info.registers_area);
594 	delete_area(info.shared_area);
595 }
596 
597