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