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