xref: /haiku/src/add-ons/accelerants/intel_extreme/mode.cpp (revision 1773f0767ed809a3c64ccc0c1037f3c8a1d5de33)
1 /*
2  * Copyright 2006-2010, Haiku, Inc. All Rights Reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Support for i915 chipset and up based on the X driver,
6  * Copyright 2006-2007 Intel Corporation.
7  *
8  * Authors:
9  *		Axel Dörfler, axeld@pinc-software.de
10  */
11 
12 
13 #include <math.h>
14 #include <stdio.h>
15 #include <string.h>
16 
17 #include <Debug.h>
18 
19 #include <create_display_modes.h>
20 #include <ddc.h>
21 #include <edid.h>
22 #include <validate_display_mode.h>
23 
24 #include "accelerant_protos.h"
25 #include "accelerant.h"
26 #include "pll.h"
27 #include "Ports.h"
28 #include "utility.h"
29 
30 
31 #undef TRACE
32 #define TRACE_MODE
33 #ifdef TRACE_MODE
34 #	define TRACE(x...) _sPrintf("intel_extreme: " x)
35 #else
36 #	define TRACE(x...)
37 #endif
38 
39 #define ERROR(x...) _sPrintf("intel_extreme: " x)
40 #define CALLED(x...) TRACE("CALLED %s\n", __PRETTY_FUNCTION__)
41 
42 
43 static void
44 get_color_space_format(const display_mode &mode, uint32 &colorMode,
45 	uint32 &bytesPerRow, uint32 &bitsPerPixel)
46 {
47 	uint32 bytesPerPixel;
48 
49 	switch (mode.space) {
50 		case B_RGB32_LITTLE:
51 			colorMode = DISPLAY_CONTROL_RGB32;
52 			bytesPerPixel = 4;
53 			bitsPerPixel = 32;
54 			break;
55 		case B_RGB16_LITTLE:
56 			colorMode = DISPLAY_CONTROL_RGB16;
57 			bytesPerPixel = 2;
58 			bitsPerPixel = 16;
59 			break;
60 		case B_RGB15_LITTLE:
61 			colorMode = DISPLAY_CONTROL_RGB15;
62 			bytesPerPixel = 2;
63 			bitsPerPixel = 15;
64 			break;
65 		case B_CMAP8:
66 		default:
67 			colorMode = DISPLAY_CONTROL_CMAP8;
68 			bytesPerPixel = 1;
69 			bitsPerPixel = 8;
70 			break;
71 	}
72 
73 	bytesPerRow = mode.virtual_width * bytesPerPixel;
74 
75 	// Make sure bytesPerRow is a multiple of 64
76 	if ((bytesPerRow & 63) != 0)
77 		bytesPerRow = (bytesPerRow + 63) & ~63;
78 }
79 
80 
81 static bool
82 sanitize_display_mode(display_mode& mode)
83 {
84 	// Some cards only support even pixel counts, while others require an odd
85 	// one.
86 	uint16 pixelCount = 1;
87 	if (gInfo->shared_info->device_type.InGroup(INTEL_GROUP_Gxx)
88 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_96x)
89 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_94x)
90 			|| gInfo->shared_info->device_type.InGroup(INTEL_GROUP_91x)
91 			|| gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_8xx)
92 			|| gInfo->shared_info->device_type.InFamily(INTEL_FAMILY_7xx)) {
93 		pixelCount = 2;
94 	}
95 
96 	// TODO: verify constraints - these are more or less taken from the
97 	// radeon driver!
98 	display_constraints constraints = {
99 		// resolution
100 		320, 8192, 200, 4096,
101 		// pixel clock
102 		gInfo->shared_info->pll_info.min_frequency,
103 		gInfo->shared_info->pll_info.max_frequency,
104 		// horizontal
105 		{pixelCount, 0, 8160, 32, 8192, 0, 8192},
106 		{1, 1, 4092, 2, 63, 1, 4096}
107 	};
108 
109 	return sanitize_display_mode(mode, constraints,
110 		gInfo->has_edid ? &gInfo->edid_info : NULL);
111 }
112 
113 
114 // #pragma mark -
115 
116 
117 static void
118 set_frame_buffer_registers(uint32 baseRegister, uint32 surfaceRegister)
119 {
120 	intel_shared_info &sharedInfo = *gInfo->shared_info;
121 	display_mode &mode = gInfo->current_mode;
122 
123 	if (sharedInfo.device_type.InGroup(INTEL_GROUP_96x)
124 		|| sharedInfo.device_type.InGroup(INTEL_GROUP_G4x)
125 		|| sharedInfo.device_type.InGroup(INTEL_GROUP_ILK)
126 		|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SER5)
127 		|| sharedInfo.device_type.InFamily(INTEL_FAMILY_SOC0)) {
128 		write32(baseRegister, mode.v_display_start * sharedInfo.bytes_per_row
129 			+ mode.h_display_start * (sharedInfo.bits_per_pixel + 7) / 8);
130 		read32(baseRegister);
131 		write32(surfaceRegister, sharedInfo.frame_buffer_offset);
132 		read32(surfaceRegister);
133 	} else {
134 		write32(baseRegister, sharedInfo.frame_buffer_offset
135 			+ mode.v_display_start * sharedInfo.bytes_per_row
136 			+ mode.h_display_start * (sharedInfo.bits_per_pixel + 7) / 8);
137 		read32(baseRegister);
138 	}
139 }
140 
141 
142 void
143 set_frame_buffer_base()
144 {
145 	// TODO we always set both displays to the same address. When we support
146 	// multiple framebuffers, they should get different addresses here.
147 	set_frame_buffer_registers(INTEL_DISPLAY_A_BASE, INTEL_DISPLAY_A_SURFACE);
148 	set_frame_buffer_registers(INTEL_DISPLAY_B_BASE, INTEL_DISPLAY_B_SURFACE);
149 }
150 
151 
152 /*!	Creates the initial mode list of the primary accelerant.
153 	It's called from intel_init_accelerant().
154 */
155 status_t
156 create_mode_list(void)
157 {
158 	CALLED();
159 
160 	for (uint32 i = 0; i < gInfo->port_count; i++) {
161 		if (gInfo->ports[i] == NULL)
162 			continue;
163 
164 		status_t status = gInfo->ports[i]->GetEDID(&gInfo->edid_info);
165 		if (status == B_OK)
166 			gInfo->has_edid = true;
167 	}
168 
169 	display_mode* list;
170 	uint32 count = 0;
171 
172 	const color_space kSupportedSpaces[] = {B_RGB32_LITTLE, B_RGB16_LITTLE,
173 		B_CMAP8};
174 	const color_space* supportedSpaces;
175 	int colorSpaceCount;
176 
177 	if (gInfo->shared_info->device_type.Generation() >= 4) {
178 		// No B_RGB15, use our custom colorspace list
179 		supportedSpaces = kSupportedSpaces;
180 		colorSpaceCount = B_COUNT_OF(kSupportedSpaces);
181 	} else {
182 		supportedSpaces = NULL;
183 		colorSpaceCount = 0;
184 	}
185 
186 	// If no EDID, but have vbt from driver, use that mode
187 	if (!gInfo->has_edid && gInfo->shared_info->got_vbt) {
188 		// We could not read any EDID info. Fallback to creating a list with
189 		// only the mode set up by the BIOS.
190 
191 		// TODO: support lower modes via scaling and windowing
192 		gInfo->mode_list_area = create_display_modes("intel extreme modes",
193 			NULL, &gInfo->shared_info->panel_mode, 1,
194 			supportedSpaces, colorSpaceCount, NULL, &list, &count);
195 	} else {
196 		// Otherwise return the 'real' list of modes
197 		gInfo->mode_list_area = create_display_modes("intel extreme modes",
198 			gInfo->has_edid ? &gInfo->edid_info : NULL, NULL, 0,
199 			supportedSpaces, colorSpaceCount, NULL, &list, &count);
200 	}
201 
202 	if (gInfo->mode_list_area < B_OK)
203 		return gInfo->mode_list_area;
204 
205 	gInfo->mode_list = list;
206 	gInfo->shared_info->mode_list_area = gInfo->mode_list_area;
207 	gInfo->shared_info->mode_count = count;
208 
209 	return B_OK;
210 }
211 
212 
213 void
214 wait_for_vblank(void)
215 {
216 	acquire_sem_etc(gInfo->shared_info->vblank_sem, 1, B_RELATIVE_TIMEOUT,
217 		25000);
218 		// With the output turned off via DPMS, we might not get any interrupts
219 		// anymore that's why we don't wait forever for it.
220 }
221 
222 
223 //	#pragma mark -
224 
225 
226 uint32
227 intel_accelerant_mode_count(void)
228 {
229 	CALLED();
230 	return gInfo->shared_info->mode_count;
231 }
232 
233 
234 status_t
235 intel_get_mode_list(display_mode* modeList)
236 {
237 	CALLED();
238 	memcpy(modeList, gInfo->mode_list,
239 		gInfo->shared_info->mode_count * sizeof(display_mode));
240 	return B_OK;
241 }
242 
243 
244 status_t
245 intel_propose_display_mode(display_mode* target, const display_mode* low,
246 	const display_mode* high)
247 {
248 	CALLED();
249 
250 	// first search for the specified mode in the list, if no mode is found
251 	// try to fix the target mode in sanitize_display_mode
252 	// TODO: Only sanitize_display_mode should be used. However, at the moments
253 	// the mode constraints are not optimal and do not work for all
254 	// configurations.
255 	for (uint32 i = 0; i < gInfo->shared_info->mode_count; i++) {
256 		display_mode *mode = &gInfo->mode_list[i];
257 
258 		// TODO: improve this, ie. adapt pixel clock to allowed values!!!
259 
260 		if (target->virtual_width != mode->virtual_width
261 			|| target->virtual_height != mode->virtual_height
262 			|| target->space != mode->space) {
263 			continue;
264 		}
265 
266 		*target = *mode;
267 		return B_OK;
268 	}
269 
270 	sanitize_display_mode(*target);
271 
272 	return is_display_mode_within_bounds(*target, *low, *high)
273 		? B_OK : B_BAD_VALUE;
274 }
275 
276 
277 status_t
278 intel_set_display_mode(display_mode* mode)
279 {
280 	if (mode == NULL)
281 		return B_BAD_VALUE;
282 
283 	TRACE("%s(%" B_PRIu16 "x%" B_PRIu16 ")\n", __func__,
284 		mode->virtual_width, mode->virtual_height);
285 
286 	display_mode target = *mode;
287 
288 	// TODO: it may be acceptable to continue when using panel fitting or
289 	// centering, since the data from propose_display_mode will not actually be
290 	// used as is in this case.
291 	if (sanitize_display_mode(target)) {
292 		TRACE("Video mode was adjusted by sanitize_display_mode\n");
293 		TRACE("Initial mode: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
294 			mode->timing.h_display, mode->timing.h_sync_start,
295 			mode->timing.h_sync_end, mode->timing.h_total,
296 			mode->timing.v_display, mode->timing.v_sync_start,
297 			mode->timing.v_sync_end, mode->timing.v_total);
298 		TRACE("Sanitized: Hd %d Hs %d He %d Ht %d Vd %d Vs %d Ve %d Vt %d\n",
299 			target.timing.h_display, target.timing.h_sync_start,
300 			target.timing.h_sync_end, target.timing.h_total,
301 			target.timing.v_display, target.timing.v_sync_start,
302 			target.timing.v_sync_end, target.timing.v_total);
303 	}
304 
305 	uint32 colorMode, bytesPerRow, bitsPerPixel;
306 	get_color_space_format(target, colorMode, bytesPerRow, bitsPerPixel);
307 
308 	// TODO: do not go further if the mode is identical to the current one.
309 	// This would avoid the screen being off when switching workspaces when they
310 	// have the same resolution.
311 
312 	intel_shared_info &sharedInfo = *gInfo->shared_info;
313 	Autolock locker(sharedInfo.accelerant_lock);
314 
315 	// First register dump
316 	//dump_registers();
317 
318 	// TODO: This may not be neccesary
319 	set_display_power_mode(B_DPMS_OFF);
320 
321 	// free old and allocate new frame buffer in graphics memory
322 
323 	intel_free_memory(sharedInfo.frame_buffer);
324 
325 	addr_t base;
326 	if (intel_allocate_memory(bytesPerRow * target.virtual_height, 0,
327 			base) < B_OK) {
328 		// oh, how did that happen? Unfortunately, there is no really good way
329 		// back
330 		if (intel_allocate_memory(gInfo->current_mode.virtual_height
331 				* sharedInfo.bytes_per_row, 0, base) == B_OK) {
332 			sharedInfo.frame_buffer = base;
333 			sharedInfo.frame_buffer_offset = base
334 				- (addr_t)sharedInfo.graphics_memory;
335 			set_frame_buffer_base();
336 		}
337 
338 		TRACE("%s: Failed to allocate framebuffer !\n", __func__);
339 		return B_NO_MEMORY;
340 	}
341 
342 	// clear frame buffer before using it
343 	memset((uint8*)base, 0, bytesPerRow * target.virtual_height);
344 	sharedInfo.frame_buffer = base;
345 	sharedInfo.frame_buffer_offset = base - (addr_t)sharedInfo.graphics_memory;
346 
347 #if 0
348 	if ((gInfo->head_mode & HEAD_MODE_TESTING) != 0) {
349 		// 1. Enable panel power as needed to retrieve panel configuration
350 		// (use AUX VDD enable bit)
351 			// skip, did detection already, might need that before that though
352 
353 		// 2. Enable PCH clock reference source and PCH SSC modulator,
354 		// wait for warmup (Can be done anytime before enabling port)
355 			// skip, most certainly already set up by bios to use other ports,
356 			// will need for coldstart though
357 
358 		// 3. If enabling CPU embedded DisplayPort A: (Can be done anytime
359 		// before enabling CPU pipe or port)
360 		//	a.	Enable PCH 120MHz clock source output to CPU, wait for DMI
361 		//		latency
362 		//	b.	Configure and enable CPU DisplayPort PLL in the DisplayPort A
363 		//		register, wait for warmup
364 			// skip, not doing eDP right now, should go into
365 			// EmbeddedDisplayPort class though
366 
367 		// 4. If enabling port on PCH: (Must be done before enabling CPU pipe
368 		// or FDI)
369 		//	a.	Enable PCH FDI Receiver PLL, wait for warmup plus DMI latency
370 		//	b.	Switch from Rawclk to PCDclk in FDI Receiver (FDI A OR FDI B)
371 		//	c.	[DevSNB] Enable CPU FDI Transmitter PLL, wait for warmup
372 		//	d.	[DevILK] CPU FDI PLL is always on and does not need to be
373 		//		enabled
374 		FDILink* link = pipe->FDILink();
375 		if (link != NULL) {
376 			link->Receiver().EnablePLL();
377 			link->Receiver().SwitchClock(true);
378 			link->Transmitter().EnablePLL();
379 		}
380 
381 		// 5. Enable CPU panel fitter if needed for hires, required for VGA
382 		// (Can be done anytime before enabling CPU pipe)
383 		PanelFitter* fitter = pipe->PanelFitter();
384 		if (fitter != NULL)
385 			fitter->Enable(mode);
386 
387 		// 6. Configure CPU pipe timings, M/N/TU, and other pipe settings
388 		// (Can be done anytime before enabling CPU pipe)
389 		pll_divisors divisors;
390 		compute_pll_divisors(target, divisors, false);
391 		pipe->ConfigureTimings(divisors);
392 
393 		// 7. Enable CPU pipe
394 		pipe->Enable();
395 
396 8. Configure and enable CPU planes (VGA or hires)
397 9. If enabling port on PCH:
398 		//	a.   Program PCH FDI Receiver TU size same as Transmitter TU size for TU error checking
399 		//	b.   Train FDI
400 		//		i. Set pre-emphasis and voltage (iterate if training steps fail)
401                     ii. Enable CPU FDI Transmitter and PCH FDI Receiver with Training Pattern 1 enabled.
402                    iii. Wait for FDI training pattern 1 time
403                    iv. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for bit lock in bit 8 (retry at least once if no lock)
404                     v. Enable training pattern 2 on CPU FDI Transmitter and PCH FDI Receiver
405                    vi.  Wait for FDI training pattern 2 time
406                   vii. Read PCH FDI Receiver ISR ([DevIBX-B+] IIR) for symbol lock in bit 9 (retry at least once if no
407                         lock)
408                   viii. Enable normal pixel output on CPU FDI Transmitter and PCH FDI Receiver
409                    ix.  Wait for FDI idle pattern time for link to become active
410          c.   Configure and enable PCH DPLL, wait for PCH DPLL warmup (Can be done anytime before enabling
411               PCH transcoder)
412          d.   [DevCPT] Configure DPLL SEL to set the DPLL to transcoder mapping and enable DPLL to the
413               transcoder.
414          e.   [DevCPT] Configure DPLL_CTL DPLL_HDMI_multipler.
415          f.   Configure PCH transcoder timings, M/N/TU, and other transcoder settings (should match CPU settings).
416          g.   [DevCPT] Configure and enable Transcoder DisplayPort Control if DisplayPort will be used
417          h.   Enable PCH transcoder
418 10. Enable ports (DisplayPort must enable in training pattern 1)
419 11. Enable panel power through panel power sequencing
420 12. Wait for panel power sequencing to reach enabled steady state
421 13. Disable panel power override
422 14. If DisplayPort, complete link training
423 15. Enable panel backlight
424 	}
425 #endif
426 
427 	// make sure VGA display is disabled
428 	write32(INTEL_VGA_DISPLAY_CONTROL, VGA_DISPLAY_DISABLED);
429 	read32(INTEL_VGA_DISPLAY_CONTROL);
430 
431 	// Go over each port and set the display mode
432 	for (uint32 i = 0; i < gInfo->port_count; i++) {
433 		if (gInfo->ports[i] == NULL)
434 			continue;
435 		if (!gInfo->ports[i]->IsConnected())
436 			continue;
437 
438 		status_t status = gInfo->ports[i]->SetDisplayMode(&target, colorMode);
439 		if (status != B_OK)
440 			ERROR("%s: Unable to set display mode!\n", __func__);
441 	}
442 
443 	TRACE("%s: Port configuration completed successfully!\n", __func__);
444 
445 	// We set the same color mode across all pipes
446 	program_pipe_color_modes(colorMode);
447 
448 	// TODO: This may not be neccesary (see DPMS OFF at top)
449 	set_display_power_mode(sharedInfo.dpms_mode);
450 
451 	// Changing bytes per row seems to be ignored if the plane/pipe is turned
452 	// off
453 
454 	// Always set both pipes, just in case
455 	// TODO rework this when we get multiple head support with different
456 	// resolutions
457 	write32(INTEL_DISPLAY_A_BYTES_PER_ROW, bytesPerRow);
458 	write32(INTEL_DISPLAY_B_BYTES_PER_ROW, bytesPerRow);
459 
460 	// update shared info
461 	gInfo->current_mode = target;
462 
463 	// TODO: move to gInfo
464 	sharedInfo.bytes_per_row = bytesPerRow;
465 	sharedInfo.bits_per_pixel = bitsPerPixel;
466 
467 	set_frame_buffer_base();
468 		// triggers writing back double-buffered registers
469 
470 	// Second register dump
471 	//dump_registers();
472 
473 	return B_OK;
474 }
475 
476 
477 status_t
478 intel_get_display_mode(display_mode* _currentMode)
479 {
480 	CALLED();
481 
482 	*_currentMode = gInfo->current_mode;
483 
484 	// This seems unreliable. We should always know the current_mode
485 	//retrieve_current_mode(*_currentMode, INTEL_DISPLAY_A_PLL);
486 	return B_OK;
487 }
488 
489 
490 status_t
491 intel_get_edid_info(void* info, size_t size, uint32* _version)
492 {
493 	CALLED();
494 
495 	if (!gInfo->has_edid)
496 		return B_ERROR;
497 	if (size < sizeof(struct edid1_info))
498 		return B_BUFFER_OVERFLOW;
499 
500 	memcpy(info, &gInfo->edid_info, sizeof(struct edid1_info));
501 	*_version = EDID_VERSION_1;
502 	return B_OK;
503 }
504 
505 
506 static int32_t
507 intel_get_backlight_register(bool read)
508 {
509 	if (gInfo->shared_info->pch_info == INTEL_PCH_NONE)
510 		return MCH_BLC_PWM_CTL;
511 
512 	if (read)
513 		return PCH_SBLC_PWM_CTL2;
514 	else
515 		return PCH_BLC_PWM_CTL;
516 }
517 
518 
519 status_t
520 intel_set_brightness(float brightness)
521 {
522 	CALLED();
523 
524 	if (brightness < 0 || brightness > 1)
525 		return B_BAD_VALUE;
526 
527 	uint32_t period = read32(intel_get_backlight_register(true)) >> 16;
528 	uint32_t duty = (uint32_t)(period * brightness) & 0xfffe;
529 		/* Setting the low bit seems to give strange results on some Atom machines */
530 	write32(intel_get_backlight_register(false), duty | (period << 16));
531 
532 	return B_OK;
533 }
534 
535 
536 status_t
537 intel_get_brightness(float* brightness)
538 {
539 	CALLED();
540 
541 	if (brightness == NULL)
542 		return B_BAD_VALUE;
543 
544 	uint16_t period = read32(intel_get_backlight_register(true)) >> 16;
545 	uint16_t   duty = read32(intel_get_backlight_register(false)) & 0xffff;
546 	*brightness = (float)duty / period;
547 
548 	return B_OK;
549 }
550 
551 
552 status_t
553 intel_get_frame_buffer_config(frame_buffer_config* config)
554 {
555 	CALLED();
556 
557 	uint32 offset = gInfo->shared_info->frame_buffer_offset;
558 
559 	config->frame_buffer = gInfo->shared_info->graphics_memory + offset;
560 	config->frame_buffer_dma
561 		= (uint8*)gInfo->shared_info->physical_graphics_memory + offset;
562 	config->bytes_per_row = gInfo->shared_info->bytes_per_row;
563 
564 	return B_OK;
565 }
566 
567 
568 status_t
569 intel_get_pixel_clock_limits(display_mode* mode, uint32* _low, uint32* _high)
570 {
571 	CALLED();
572 
573 	if (_low != NULL) {
574 		// lower limit of about 48Hz vertical refresh
575 		uint32 totalClocks = (uint32)mode->timing.h_total
576 			* (uint32)mode->timing.v_total;
577 		uint32 low = (totalClocks * 48L) / 1000L;
578 		if (low < gInfo->shared_info->pll_info.min_frequency)
579 			low = gInfo->shared_info->pll_info.min_frequency;
580 		else if (low > gInfo->shared_info->pll_info.max_frequency)
581 			return B_ERROR;
582 
583 		*_low = low;
584 	}
585 
586 	if (_high != NULL)
587 		*_high = gInfo->shared_info->pll_info.max_frequency;
588 
589 	return B_OK;
590 }
591 
592 
593 status_t
594 intel_move_display(uint16 horizontalStart, uint16 verticalStart)
595 {
596 	CALLED();
597 
598 	intel_shared_info &sharedInfo = *gInfo->shared_info;
599 	Autolock locker(sharedInfo.accelerant_lock);
600 
601 	display_mode &mode = gInfo->current_mode;
602 
603 	if (horizontalStart + mode.timing.h_display > mode.virtual_width
604 		|| verticalStart + mode.timing.v_display > mode.virtual_height)
605 		return B_BAD_VALUE;
606 
607 	mode.h_display_start = horizontalStart;
608 	mode.v_display_start = verticalStart;
609 
610 	set_frame_buffer_base();
611 
612 	return B_OK;
613 }
614 
615 
616 status_t
617 intel_get_timing_constraints(display_timing_constraints* constraints)
618 {
619 	CALLED();
620 	return B_ERROR;
621 }
622 
623 
624 void
625 intel_set_indexed_colors(uint count, uint8 first, uint8* colors, uint32 flags)
626 {
627 	TRACE("%s(colors = %p, first = %u)\n", __func__, colors, first);
628 
629 	if (colors == NULL)
630 		return;
631 
632 	Autolock locker(gInfo->shared_info->accelerant_lock);
633 
634 	for (; count-- > 0; first++) {
635 		uint32 color = colors[0] << 16 | colors[1] << 8 | colors[2];
636 		colors += 3;
637 
638 		write32(INTEL_DISPLAY_A_PALETTE + first * sizeof(uint32), color);
639 		write32(INTEL_DISPLAY_B_PALETTE + first * sizeof(uint32), color);
640 	}
641 }
642