xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp (revision a5c0d1a80e18f50987966fda2005210092d7671b)
1 /*
2  * Copyright 2012-2014, Artem Falcon <lomka@gero.in>
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include <string.h>
8 
9 #include <KernelExport.h>
10 #include <directories.h>
11 #include <stdio.h>
12 #include <driver_settings.h>
13 
14 #include "intel_extreme.h"
15 #include "driver.h"
16 
17 
18 #define TRACE_BIOS
19 #ifdef TRACE_BIOS
20 #	define TRACE(x) dprintf x
21 #else
22 #	define TRACE(x) ;
23 #endif
24 
25 
26 struct vbt_header {
27 	uint8 signature[20];
28 	uint16 version;
29 	uint16 header_size;
30 	uint16 vbt_size;
31 	uint8 vbt_checksum;
32 	uint8 reserved0;
33 	uint32 bdb_offset;
34 	uint32 aim_offset[4];
35 } __attribute__((packed));
36 
37 
38 struct bdb_header {
39 	uint8 signature[16];
40 	uint16 version;
41 	uint16 header_size;
42 	uint16 bdb_size;
43 } __attribute__((packed));
44 
45 
46 enum bdb_block_id {
47 	BDB_GENERAL_DEFINITIONS = 2,
48 	BDB_LVDS_OPTIONS = 40,
49 	BDB_LVDS_LFP_DATA_PTRS = 41,
50 	BDB_LVDS_BACKLIGHT = 43,
51 	BDB_GENERIC_DTD = 58
52 };
53 
54 
55 struct bdb_general_definitions {
56 	uint8 id;
57 	uint16 size;
58 	uint8 crt_ddc_gmbus_pin;
59 	uint8 dpms_bits;
60 	uint8 boot_display[2];
61 	uint8 child_device_size;
62 	uint8 devices[];
63 } __attribute__((packed));
64 
65 
66 // FIXME the struct definition for the bdb_header is not complete, so we rely
67 // on direct access with hardcoded offsets to get the timings out of it.
68 #define _PIXEL_CLOCK(x) (x[0] + (x[1] << 8)) * 10000
69 #define _H_ACTIVE(x) (x[2] + ((x[4] & 0xF0) << 4))
70 #define _H_BLANK(x) (x[3] + ((x[4] & 0x0F) << 8))
71 #define _H_SYNC_OFF(x) (x[8] + ((x[11] & 0xC0) << 2))
72 #define _H_SYNC_WIDTH(x) (x[9] + ((x[11] & 0x30) << 4))
73 #define _V_ACTIVE(x) (x[5] + ((x[7] & 0xF0) << 4))
74 #define _V_BLANK(x) (x[6] + ((x[7] & 0x0F) << 8))
75 #define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2))
76 #define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4))
77 
78 #define BDB_BACKLIGHT_TYPE_NONE 0
79 #define BDB_BACKLIGHT_TYPE_PWM 2
80 
81 
82 struct lvds_bdb1 {
83 	uint8 id;
84 	uint16 size;
85 	uint8 panel_type;
86 	uint8 reserved0;
87 	uint16 caps;
88 } __attribute__((packed));
89 
90 
91 struct lvds_bdb2_entry {
92 	uint16 lfp_info_offset;
93 	uint8 lfp_info_size;
94 	uint16 lfp_edid_dtd_offset;
95 	uint8 lfp_edid_dtd_size;
96 	uint16 lfp_edid_pid_offset;
97 	uint8 lfp_edid_pid_size;
98 } __attribute__((packed));
99 
100 
101 struct lvds_bdb2 {
102 	uint8 id;
103 	uint16 size;
104 	uint8 table_size; /* followed by one or more lvds_data_ptr structs */
105 	struct lvds_bdb2_entry panels[16];
106 } __attribute__((packed));
107 
108 
109 struct lvds_bdb2_lfp_info {
110 	uint16 x_res;
111 	uint16 y_res;
112 	uint32 lvds_reg;
113 	uint32 lvds_reg_val;
114 	uint32 pp_on_reg;
115 	uint32 pp_on_reg_val;
116 	uint32 pp_off_reg;
117 	uint32 pp_off_reg_val;
118 	uint32 pp_cycle_reg;
119 	uint32 pp_cycle_reg_val;
120 	uint32 pfit_reg;
121 	uint32 pfit_reg_val;
122 	uint16 terminator;
123 } __attribute__((packed));
124 
125 
126 
127 struct generic_dtd_entry {
128 	uint32 pixel_clock;
129 	uint16 hactive;
130 	uint16 hblank;
131 	uint16 hfront_porch;
132 	uint16 hsync;
133 	uint16 vactive;
134 	uint16 vblank;
135 	uint16 vfront_porch;
136 	uint16 vsync;
137 	uint16 width_mm;
138 	uint16 height_mm;
139 
140 	uint8 rsvd_flags:6;
141 	uint8 vsync_positive_polarity:1;
142 	uint8 hsync_positive_polarity:1;
143 
144 	uint8 rsvd[3];
145 } __attribute__((packed));
146 
147 
148 struct bdb_generic_dtd {
149 	uint8 id;
150 	uint16 size;
151 	uint16 gdtd_size;
152 	struct generic_dtd_entry dtd[];
153 } __attribute__((packed));
154 
155 
156 struct bdb_lfp_backlight_data_entry {
157 	uint8 type: 2;
158 	uint8 active_low_pwm: 1;
159 	uint8 reserved1: 5;
160 	uint16 pwm_freq_hz;
161 	uint8 min_brightness; // Versions < 234
162 	uint8 reserved2;
163 	uint8 reserved3;
164 } __attribute__((packed));
165 
166 
167 struct bdb_lfp_backlight_control_method {
168 	uint8 type: 4;
169 	uint8 controller: 4;
170 } __attribute__((packed));
171 
172 
173 struct lfp_brightness_level {
174 	uint16 level;
175 	uint16 reserved;
176 } __attribute__((packed));
177 
178 
179 struct bdb_lfp_backlight_data {
180 	uint8 entry_size;
181 	struct bdb_lfp_backlight_data_entry data[16];
182 	uint8 level [16]; // Only for versions < 234
183 	struct bdb_lfp_backlight_control_method backlight_control[16];
184 	struct lfp_brightness_level brightness_level[16]; // Versions >= 234
185 	struct lfp_brightness_level brightness_min_level[16]; // Versions >= 234
186 	uint8 brightness_precision_bits[16]; // Versions >= 236
187 } __attribute__((packed));
188 
189 
190 static struct vbios {
191 	area_id			area;
192 	uint8*			memory;
193 	uint16_t ReadWord(off_t address)
194 	{
195 		return memory[address] | memory[address + 1] << 8;
196 	}
197 } vbios;
198 
199 
200 static bool
201 read_settings_dumpRom(void)
202 {
203 	bool dumpRom = false;
204 
205 	void* settings = load_driver_settings("intel_extreme");
206 	if (settings != NULL) {
207 		dumpRom = get_driver_boolean_parameter(settings,
208 			"dump_rom", false, false);
209 
210 		unload_driver_settings(settings);
211 	}
212 	return dumpRom;
213 }
214 
215 
216 static void
217 dumprom(void *rom, uint32 size, intel_info &info)
218 {
219 	int fd;
220 	uint32 cnt;
221 	char fname[64];
222 
223 	/* determine the romfile name: we need split-up per card in the system */
224 	sprintf (fname, kUserDirectory "//intel_extreme.%04x_%04x_%02x%02x%02x.rom",
225 		info.pci->vendor_id, info.pci->device_id, info.pci->bus, info.pci->device, info.pci->function);
226 
227 	fd = open (fname, O_WRONLY | O_CREAT, 0666);
228 	if (fd < 0) return;
229 
230 	/* The ROM size is a multiple of 1kb.. */
231 	for (cnt = 0; (cnt < size); cnt += 1024)
232 		write (fd, ((void *)(((uint8 *)rom) + cnt)), 1024);
233 	close (fd);
234 }
235 
236 
237 /*!	This is reimplementation, Haiku uses BIOS call and gets most current panel
238 	info, we're, otherwise, digging in VBIOS memory and parsing VBT tables to
239 	get native panel timings. This will allow to get non-updated,
240 	PROM-programmed timings info when compensation mode is off on your machine.
241 */
242 
243 // outdated: https://01.org/sites/default/files/documentation/skl_opregion_rev0p5.pdf
244 
245 #define ASLS 0xfc // ASL Storage.
246 #define OPREGION_SIGNATURE "IntelGraphicsMem"
247 #define OPREGION_ASLE_OFFSET   0x300
248 #define OPREGION_VBT_OFFSET   0x400
249 #define MBOX_ACPI (1 << 0)
250 #define MBOX_SWSCI (1 << 1)
251 #define MBOX_ASLE (1 << 2)
252 #define MBOX_ASLE_EXT (1 << 3)
253 
254 
255 struct opregion_header {
256 	uint8 signature[16];
257 	uint32 size;
258 	uint8 reserved;
259 	uint8 revision_version;
260 	uint8 minor_version;
261 	uint8 major_version;
262 	uint8 sver[32];
263 	uint8 vver[16];
264 	uint8 gver[16];
265 	uint32 mboxes;
266 	uint32 driver_model;
267 	uint32 platform_configuration;
268 	uint8 gop_version[32];
269 	uint8 rsvd[124];
270 } __attribute__((packed));
271 
272 
273 struct opregion_asle {
274 	uint32 ardy;
275 	uint32 aslc;
276 	uint32 tche;
277 	uint32 alsi;
278 	uint32 bclp;
279 	uint32 pfit;
280 	uint32 cblv;
281 	uint16 bclm[20];
282 	uint32 cpfm;
283 	uint32 epfm;
284 	uint8 plut[74];
285 	uint32 pfmb;
286 	uint32 cddv;
287 	uint32 pcft;
288 	uint32 srot;
289 	uint32 iuer;
290 	uint64 fdss;
291 	uint32 fdsp;
292 	uint32 stat;
293 	uint64 rvda;
294 	uint32 rvds;
295 	uint8 rsvd[58];
296 } __attribute__((packed));
297 
298 
299 static bool
300 get_bios(int* vbtOffset)
301 {
302 	STATIC_ASSERT(sizeof(opregion_header) == 0x100);
303 	STATIC_ASSERT(sizeof(opregion_asle) == 0x100);
304 
305 	intel_info &info = *gDeviceInfo[0];
306 	// first try to fetch Intel OpRegion which should be populated by the BIOS at start
307 	uint32 kVBIOSSize;
308 
309 	for (uint32 romMethod = 0; romMethod < 2; romMethod++) {
310 		switch(romMethod) {
311 			case 0:
312 			{
313 				// get OpRegion - see Intel ACPI IGD info in acpi_igd_opregion_spec_0.pdf
314 				uint64 kVBIOSAddress = get_pci_config(info.pci, ASLS, 4);
315 				if (kVBIOSAddress == 0) {
316 					TRACE((DEVICE_NAME ": ACPI OpRegion not supported!\n"));
317 					continue;
318 				}
319 				kVBIOSSize = 8 * 1024;
320 				TRACE((DEVICE_NAME ": Graphic OpRegion physical addr: 0x%" B_PRIx64
321 						"; size: 0x%" B_PRIx32 "\n", kVBIOSAddress, kVBIOSSize));
322 				vbios.area = map_physical_memory("ASLS mapping", kVBIOSAddress,
323 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void **)&vbios.memory);
324 				if (vbios.area < 0)
325 					continue;
326 				TRACE((DEVICE_NAME ": mapping OpRegion: 0x%" B_PRIx64 " -> %p\n",
327 					kVBIOSAddress, vbios.memory));
328 				// check if we got the OpRegion and signature
329 				if (memcmp(vbios.memory, OPREGION_SIGNATURE, 16) != 0) {
330 					TRACE((DEVICE_NAME ": OpRegion signature mismatch\n"));
331 					delete_area(vbios.area);
332 					vbios.area = -1;
333 					continue;
334 				}
335 				opregion_header* header = (opregion_header*)vbios.memory;
336 				opregion_asle* asle = (opregion_asle*)(vbios.memory + OPREGION_ASLE_OFFSET);
337 				if (header->major_version < 2 || (header->mboxes & MBOX_ASLE) == 0
338 					|| asle->rvda == 0 || asle->rvds == 0) {
339 					vbios.memory += OPREGION_VBT_OFFSET;
340 					kVBIOSSize -= OPREGION_VBT_OFFSET;
341 					break;
342 				}
343 				uint64 rvda = asle->rvda;
344 				kVBIOSSize = asle->rvds;
345 				if (header->major_version > 3 || header->minor_version >= 1) {
346 					rvda += kVBIOSAddress;
347 				}
348 				TRACE((DEVICE_NAME ": RVDA physical addr: 0x%" B_PRIx64
349 						"; size: 0x%" B_PRIx32 "\n", rvda, kVBIOSSize));
350 				delete_area(vbios.area);
351 				vbios.area = map_physical_memory("RVDA mapping", rvda,
352 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void **)&vbios.memory);
353 				if (vbios.area < 0)
354 					continue;
355 				break;
356 			}
357 			case 1:
358 			{
359 				uint64 kVBIOSAddress = 0xc0000;
360 				kVBIOSSize = 64 * 1024;
361 				/* !!!DANGER!!!: mapping of BIOS using legacy location as a fallback,
362 				  hence, if panel mode will be set using info from VBT this way, it will
363 				  be taken from primary card's VBIOS */
364 				vbios.area = map_physical_memory("VBIOS mapping", kVBIOSAddress,
365 					kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void**)&vbios.memory);
366 				if (vbios.area < 0)
367 					continue;
368 
369 				TRACE((DEVICE_NAME ": mapping VBIOS: 0x%" B_PRIx64 " -> %p\n",
370 					kVBIOSAddress, vbios.memory));
371 				break;
372 			}
373 		}
374 
375 		// dump ROM to file if selected in settings file
376 		if (read_settings_dumpRom())
377 			dumprom(vbios.memory, kVBIOSSize, info);
378 
379 		// scan BIOS for VBT signature
380 		*vbtOffset = kVBIOSSize;
381 		for (uint32 i = 0; i + 4 < kVBIOSSize; i += 4) {
382 			if (memcmp(vbios.memory + i, "$VBT", 4) == 0) {
383 				*vbtOffset = i;
384 				break;
385 			}
386 		}
387 
388 		if ((*vbtOffset + (uint32)sizeof(vbt_header)) >= kVBIOSSize) {
389 			TRACE((DEVICE_NAME": bad VBT offset : 0x%x\n", *vbtOffset));
390 			delete_area(vbios.area);
391 			continue;
392 		}
393 
394 		struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + *vbtOffset);
395 		if (memcmp(vbt->signature, "$VBT", 4) != 0) {
396 			TRACE((DEVICE_NAME": bad VBT signature: %20s\n", vbt->signature));
397 			delete_area(vbios.area);
398 			continue;
399 		}
400 		return true;
401 	}
402 
403 	return false;
404 }
405 
406 
407 static void
408 sanitize_panel_timing(display_timing& timing)
409 {
410 	bool bogus = false;
411 
412 	/* handle bogus h/vtotal values, if got such */
413 	if (timing.h_sync_end > timing.h_total) {
414 		timing.h_total = timing.h_sync_end + 1;
415 		bogus = true;
416 		TRACE((DEVICE_NAME": got bogus htotal. Fixing\n"));
417 	}
418 	if (timing.v_sync_end > timing.v_total) {
419 		timing.v_total = timing.v_sync_end + 1;
420 		bogus = true;
421 		TRACE((DEVICE_NAME": got bogus vtotal. Fixing\n"));
422 	}
423 
424 	if (bogus) {
425 		TRACE((DEVICE_NAME": adjusted LFP modeline: %" B_PRIu32 " KHz,\t"
426 			"%d %d %d %d   %d %d %d %d\n",
427 			timing.pixel_clock / (timing.h_total * timing.v_total),
428 			timing.h_display, timing.h_sync_start,
429 			timing.h_sync_end, timing.h_total,
430 			timing.v_display, timing.v_sync_start,
431 			timing.v_sync_end, timing.v_total));
432 	}
433 }
434 
435 
436 bool
437 parse_vbt_from_bios(struct intel_shared_info* info)
438 {
439 	display_timing* panelTiming = &info->panel_timing;
440 	uint16* minBrightness = &info->min_brightness;
441 
442 	int vbtOffset = 0;
443 	if (!get_bios(&vbtOffset))
444 		return false;
445 
446 	struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset);
447 	int bdbOffset = vbtOffset + vbt->bdb_offset;
448 
449 	struct bdb_header* bdb = (struct bdb_header*)(vbios.memory + bdbOffset);
450 	if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) {
451 		TRACE((DEVICE_NAME": bad BDB signature\n"));
452 		delete_area(vbios.area);
453 	}
454 	TRACE((DEVICE_NAME ": VBT signature \"%.*s\", BDB version %d\n",
455 			(int)sizeof(vbt->signature), vbt->signature, bdb->version));
456 
457 	int blockSize;
458 	int panelType = -1;
459 	bool panelTimingFound = false;
460 
461 	for (int bdbBlockOffset = bdb->header_size; bdbBlockOffset < bdb->bdb_size;
462 			bdbBlockOffset += blockSize) {
463 		int start = bdbOffset + bdbBlockOffset;
464 
465 		int id = vbios.memory[start];
466 		blockSize = vbios.ReadWord(start + 1) + 3;
467 		switch (id) {
468 			case BDB_GENERAL_DEFINITIONS:
469 			{
470 				info->device_config_count = 0;
471 				struct bdb_general_definitions* defs;
472 				if (bdb->version < 111)
473 					break;
474 				defs = (struct bdb_general_definitions*)(vbios.memory + start);
475 				uint8 childDeviceSize = defs->child_device_size;
476 				uint32 device_config_count = (blockSize - sizeof(*defs)) / childDeviceSize;
477 				for (uint32 i = 0; i < device_config_count; i++) {
478 					child_device_config* config =
479 						(child_device_config*)(&defs->devices[i * childDeviceSize]);
480 					if (config->device_type == 0)
481 						continue;
482 					memcpy(&info->device_configs[info->device_config_count], config,
483 						min_c(sizeof(child_device_config), childDeviceSize));
484 					TRACE((DEVICE_NAME ": found child device type: 0x%x\n", config->device_type));
485 					info->device_config_count++;
486 					if (info->device_config_count > 10)
487 						break;
488 				}
489 				break;
490 			}
491 			case BDB_LVDS_OPTIONS:
492 			{
493 				struct lvds_bdb1 *lvds1;
494 				lvds1 = (struct lvds_bdb1 *)(vbios.memory + start);
495 				panelType = lvds1->panel_type;
496 				if (panelType > 0xf) {
497 					TRACE((DEVICE_NAME ": invalid panel type %d\n", panelType));
498 					panelType = -1;
499 					break;
500 				}
501 				TRACE((DEVICE_NAME ": panel type: %d\n", panelType));
502 				break;
503 			}
504 			case BDB_LVDS_LFP_DATA_PTRS:
505 			{
506 				// First make sure we found block BDB_LVDS_OPTIONS and the panel type
507 				if (panelType == -1)
508 					break;
509 
510 				// on newer versions, check also generic DTD, use LFP panel DTD as a fallback
511 				if (bdb->version >= 229 && panelTimingFound)
512 					break;
513 
514 				struct lvds_bdb2 *lvds2;
515 				struct lvds_bdb2_lfp_info *lvds2_lfp_info;
516 
517 				lvds2 = (struct lvds_bdb2 *)(vbios.memory + start);
518 				lvds2_lfp_info = (struct lvds_bdb2_lfp_info *)
519 					(vbios.memory + bdbOffset
520 					+ lvds2->panels[panelType].lfp_info_offset);
521 				/* Show terminator: Check not done in drm i915 driver: Assuming chk not valid. */
522 				TRACE((DEVICE_NAME ": LFP info terminator %x\n", lvds2_lfp_info->terminator));
523 
524 				uint8_t* timing_data = vbios.memory + bdbOffset
525 					+ lvds2->panels[panelType].lfp_edid_dtd_offset;
526 				TRACE((DEVICE_NAME ": found LFP of size %d x %d "
527 					"in BIOS VBT tables\n",
528 					lvds2_lfp_info->x_res, lvds2_lfp_info->y_res));
529 
530 				panelTiming->pixel_clock = _PIXEL_CLOCK(timing_data) / 1000;
531 				panelTiming->h_sync_start = _H_ACTIVE(timing_data) + _H_SYNC_OFF(timing_data);
532 				panelTiming->h_sync_end = panelTiming->h_sync_start + _H_SYNC_WIDTH(timing_data);
533 				panelTiming->h_total = _H_ACTIVE(timing_data) + _H_BLANK(timing_data);
534 				panelTiming->h_display = _H_ACTIVE(timing_data);
535 				panelTiming->v_sync_start = _V_ACTIVE(timing_data) + _V_SYNC_OFF(timing_data);
536 				panelTiming->v_sync_end = panelTiming->v_sync_start + _V_SYNC_WIDTH(timing_data);
537 				panelTiming->v_total = _V_ACTIVE(timing_data) + _V_BLANK(timing_data);
538 				panelTiming->v_display = _V_ACTIVE(timing_data);
539 				panelTiming->flags = 0;
540 
541 				sanitize_panel_timing(*panelTiming);
542 
543 				panelTimingFound = true;
544 				break;
545 
546 			}
547 			case BDB_GENERIC_DTD:
548 			{
549 				// First make sure we found block BDB_LVDS_OPTIONS and the panel type
550 				if (panelType == -1)
551 					break;
552 
553 				bdb_generic_dtd* generic_dtd = (bdb_generic_dtd*)(vbios.memory + start);
554 				if (generic_dtd->gdtd_size < sizeof(bdb_generic_dtd)) {
555 					TRACE((DEVICE_NAME ": invalid gdtd_size %d\n", generic_dtd->gdtd_size));
556 					break;
557 				}
558 				int32 count = (blockSize - sizeof(bdb_generic_dtd)) / generic_dtd->gdtd_size;
559 				if (panelType >= count) {
560 					TRACE((DEVICE_NAME ": panel type not found %d in %" B_PRId32 " dtds\n",
561 						panelType, count));
562 					break;
563 				}
564 				generic_dtd_entry* dtd = &generic_dtd->dtd[panelType];
565 				TRACE((DEVICE_NAME ": pixel_clock %" B_PRId32 " "
566 					"hactive %d hfront_porch %d hsync %d hblank %d "
567 					"vactive %d vfront_porch %d vsync %d vblank %d\n",
568 						dtd->pixel_clock, dtd->hactive, dtd->hfront_porch, dtd->hsync, dtd->hblank,
569 						dtd->vactive, dtd->vfront_porch, dtd->vsync, dtd->vblank));
570 
571 				TRACE((DEVICE_NAME ": found generic dtd entry of size %d x %d "
572 					"in BIOS VBT tables\n", dtd->hactive, dtd->vactive));
573 
574 				panelTiming->pixel_clock = dtd->pixel_clock;
575 				panelTiming->h_sync_start = dtd->hactive + dtd->hfront_porch;
576 				panelTiming->h_sync_end = panelTiming->h_sync_start + dtd->hsync;
577 				panelTiming->h_total = dtd->hactive + dtd->hblank;
578 				panelTiming->h_display = dtd->hactive;
579 				panelTiming->v_sync_start = dtd->vactive + dtd->vfront_porch;
580 				panelTiming->v_sync_end = panelTiming->v_sync_start + dtd->vsync;
581 				panelTiming->v_total = dtd->vactive + dtd->vblank;
582 				panelTiming->v_display = dtd->vactive;
583 				panelTiming->flags = 0;
584 				if (dtd->hsync_positive_polarity)
585 					panelTiming->flags |= B_POSITIVE_HSYNC;
586 				if (dtd->vsync_positive_polarity)
587 					panelTiming->flags |= B_POSITIVE_VSYNC;
588 
589 				sanitize_panel_timing(*panelTiming);
590 				panelTimingFound = true;
591 				break;
592 			}
593 			case BDB_LVDS_BACKLIGHT:
594 			{
595 				TRACE((DEVICE_NAME ": found bdb lvds backlight info\n"));
596 				// First make sure we found block BDB_LVDS_OPTIONS and the panel type
597 				if (panelType == -1)
598 					break;
599 
600 				bdb_lfp_backlight_data* backlightData
601 					= (bdb_lfp_backlight_data*)(vbios.memory + start);
602 
603 				const struct bdb_lfp_backlight_data_entry* entry = &backlightData->data[panelType];
604 
605 				if (entry->type == BDB_BACKLIGHT_TYPE_PWM) {
606 					uint16 minLevel;
607 					if (bdb->version < 234) {
608 						minLevel = entry->min_brightness;
609 					} else {
610 						minLevel = backlightData->brightness_min_level[panelType].level;
611 						if (bdb->version >= 236
612 							&& backlightData->brightness_precision_bits[panelType] == 16) {
613 							TRACE((DEVICE_NAME ": divide level by 255\n"));
614 							minLevel /= 255;
615 						}
616 					}
617 
618 					*minBrightness = minLevel;
619 					TRACE((DEVICE_NAME ": display %d min brightness level is %u\n", panelType,
620 						minLevel));
621 				} else {
622 					TRACE((DEVICE_NAME ": display %d does not have PWM\n", panelType));
623 				}
624 				break;
625 			}
626 		}
627 	}
628 
629 	delete_area(vbios.area);
630 	return panelTimingFound;
631 }
632