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 11 #include "intel_extreme.h" 12 13 14 #define TRACE_BIOS 15 #ifdef TRACE_BIOS 16 # define TRACE(x) dprintf x 17 #else 18 # define TRACE(x) ; 19 #endif 20 21 22 static const off_t kVbtPointer = 0x1A; 23 24 25 struct vbt_header { 26 uint8 signature[20]; 27 uint16 version; 28 uint16 header_size; 29 uint16 vbt_size; 30 uint8 vbt_checksum; 31 uint8 reserved0; 32 uint32 bdb_offset; 33 uint32 aim_offset[4]; 34 } __attribute__((packed)); 35 36 37 struct bdb_header { 38 uint8 signature[16]; 39 uint16 version; 40 uint16 header_size; 41 uint16 bdb_size; 42 } __attribute__((packed)); 43 44 45 // FIXME the struct definition for the bdb_header is not complete, so we rely 46 // on direct access with hardcoded offsets to get the timings out of it. 47 #define _PIXEL_CLOCK(x) (x[0] + (x[1] << 8)) * 10000 48 #define _H_ACTIVE(x) (x[2] + ((x[4] & 0xF0) << 4)) 49 #define _H_BLANK(x) (x[3] + ((x[4] & 0x0F) << 8)) 50 #define _H_SYNC_OFF(x) (x[8] + ((x[11] & 0xC0) << 2)) 51 #define _H_SYNC_WIDTH(x) (x[9] + ((x[11] & 0x30) << 4)) 52 #define _V_ACTIVE(x) (x[5] + ((x[7] & 0xF0) << 4)) 53 #define _V_BLANK(x) (x[6] + ((x[7] & 0x0F) << 8)) 54 #define _V_SYNC_OFF(x) ((x[10] >> 4) + ((x[11] & 0x0C) << 2)) 55 #define _V_SYNC_WIDTH(x) ((x[10] & 0x0F) + ((x[11] & 0x03) << 4)) 56 57 58 struct lvds_bdb1 { 59 uint8 id; 60 uint16 size; 61 uint8 panel_type; 62 uint8 reserved0; 63 uint16 caps; 64 } __attribute__((packed)); 65 66 67 struct lvds_bdb2_entry { 68 uint16 lfp_info_offset; 69 uint8 lfp_info_size; 70 uint16 lfp_edid_dtd_offset; 71 uint8 lfp_edid_dtd_size; 72 uint16 lfp_edid_pid_offset; 73 uint8 lfp_edid_pid_size; 74 } __attribute__((packed)); 75 76 77 struct lvds_bdb2 { 78 uint8 id; 79 uint16 size; 80 uint8 table_size; /* followed by one or more lvds_data_ptr structs */ 81 struct lvds_bdb2_entry panels[16]; 82 } __attribute__((packed)); 83 84 85 struct lvds_bdb2_lfp_info { 86 uint16 x_res; 87 uint16 y_res; 88 uint32 lvds_reg; 89 uint32 lvds_reg_val; 90 uint32 pp_on_reg; 91 uint32 pp_on_reg_val; 92 uint32 pp_off_reg; 93 uint32 pp_off_reg_val; 94 uint32 pp_cycle_reg; 95 uint32 pp_cycle_reg_val; 96 uint32 pfit_reg; 97 uint32 pfit_reg_val; 98 uint16 terminator; 99 } __attribute__((packed)); 100 101 102 static struct vbios { 103 area_id area; 104 uint8* memory; 105 display_mode* shared_info; 106 struct { 107 uint16 hsync_start; 108 uint16 hsync_end; 109 uint16 hsync_total; 110 uint16 vsync_start; 111 uint16 vsync_end; 112 uint16 vsync_total; 113 } timings_common; 114 115 uint16_t ReadWord(off_t address) 116 { 117 return memory[address] | memory[address + 1] << 8; 118 } 119 } vbios; 120 121 122 /*! This is reimplementation, Haiku uses BIOS call and gets most current panel 123 info, we're, otherwise, digging in VBIOS memory and parsing VBT tables to 124 get native panel timings. This will allow to get non-updated, 125 PROM-programmed timings info when compensation mode is off on your machine. 126 */ 127 static bool 128 get_bios(void) 129 { 130 static const uint64_t kVBIOSAddress = 0xc0000; 131 static const int kVBIOSSize = 64 * 1024; 132 // FIXME: is this the same for all cards? 133 134 /* !!!DANGER!!!: mapping of BIOS using legacy location for now, 135 hence, if panel mode will be set using info from VBT, it will 136 be taken from primary card's VBIOS */ 137 vbios.area = map_physical_memory("VBIOS mapping", kVBIOSAddress, 138 kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_KERNEL_READ_AREA, (void**)&vbios.memory); 139 140 if (vbios.area < 0) 141 return false; 142 143 TRACE((DEVICE_NAME ": mapping VBIOS: 0x%" B_PRIx64 " -> %p\n", 144 kVBIOSAddress, vbios.memory)); 145 146 int vbtOffset = vbios.ReadWord(kVbtPointer); 147 if ((vbtOffset + (int)sizeof(vbt_header)) >= kVBIOSSize) { 148 TRACE((DEVICE_NAME": bad VBT offset : 0x%x\n", vbtOffset)); 149 delete_area(vbios.area); 150 return false; 151 } 152 153 struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset); 154 if (memcmp(vbt->signature, "$VBT", 4) != 0) { 155 TRACE((DEVICE_NAME": bad VBT signature: %20s\n", vbt->signature)); 156 delete_area(vbios.area); 157 return false; 158 } 159 return true; 160 } 161 162 163 static bool 164 feed_shared_info(uint8* data) 165 { 166 bool bogus = false; 167 168 /* handle bogus h/vtotal values, if got such */ 169 if (vbios.timings_common.hsync_end > vbios.timings_common.hsync_total) { 170 vbios.timings_common.hsync_total = vbios.timings_common.hsync_end + 1; 171 bogus = true; 172 TRACE((DEVICE_NAME": got bogus htotal. Fixing\n")); 173 } 174 if (vbios.timings_common.vsync_end > vbios.timings_common.vsync_total) { 175 vbios.timings_common.vsync_total = vbios.timings_common.vsync_end + 1; 176 bogus = true; 177 TRACE((DEVICE_NAME": got bogus vtotal. Fixing\n")); 178 } 179 180 if (bogus) { 181 TRACE((DEVICE_NAME": adjusted LFP modeline: x%d Hz,\t" 182 "%d %d %d %d %d %d %d %d\n", 183 _PIXEL_CLOCK(data) / ((_H_ACTIVE(data) + _H_BLANK(data)) 184 * (_V_ACTIVE(data) + _V_BLANK(data))), 185 _H_ACTIVE(data), vbios.timings_common.hsync_start, 186 vbios.timings_common.hsync_end, vbios.timings_common.hsync_total, 187 _V_ACTIVE(data), vbios.timings_common.vsync_start, 188 vbios.timings_common.vsync_end, vbios.timings_common.vsync_total)); 189 } 190 191 /* TODO: add retrieved info to edid info struct, not fixed mode struct */ 192 193 /* struct display_timing is not packed, so we have to set elements 194 individually */ 195 vbios.shared_info->timing.pixel_clock = _PIXEL_CLOCK(data) / 1000; 196 vbios.shared_info->timing.h_display = vbios.shared_info->virtual_width 197 = _H_ACTIVE(data); 198 vbios.shared_info->timing.h_sync_start = vbios.timings_common.hsync_start; 199 vbios.shared_info->timing.h_sync_end = vbios.timings_common.hsync_end; 200 vbios.shared_info->timing.h_total = vbios.timings_common.hsync_total; 201 vbios.shared_info->timing.v_display = vbios.shared_info->virtual_height 202 = _V_ACTIVE(data); 203 vbios.shared_info->timing.v_sync_start = vbios.timings_common.vsync_start; 204 vbios.shared_info->timing.v_sync_end = vbios.timings_common.vsync_end; 205 vbios.shared_info->timing.v_total = vbios.timings_common.vsync_total; 206 207 delete_area(vbios.area); 208 return true; 209 } 210 211 212 bool 213 get_lvds_mode_from_bios(display_mode* sharedInfo) 214 { 215 if (!get_bios()) 216 return false; 217 218 int vbtOffset = vbios.ReadWord(kVbtPointer); 219 struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset); 220 int bdbOffset = vbtOffset + vbt->bdb_offset; 221 222 struct bdb_header* bdb = (struct bdb_header*)(vbios.memory + bdbOffset); 223 if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) { 224 TRACE((DEVICE_NAME": bad BDB signature\n")); 225 delete_area(vbios.area); 226 } 227 228 int blockSize; 229 int panelType = -1; 230 231 for (int bdbBlockOffset = bdb->header_size; bdbBlockOffset < bdb->bdb_size; 232 bdbBlockOffset += blockSize) { 233 int start = bdbOffset + bdbBlockOffset; 234 235 int id = vbios.memory[start]; 236 blockSize = vbios.ReadWord(start + 1) + 3; 237 switch (id) { 238 case 40: // FIXME magic numbers 239 { 240 struct lvds_bdb1 *lvds1; 241 lvds1 = (struct lvds_bdb1 *)(vbios.memory + start); 242 panelType = lvds1->panel_type; 243 TRACE((DEVICE_NAME ": panel type: %d\n", panelType)); 244 break; 245 } 246 case 41: 247 { 248 // First make sure we found block 40 and the panel type 249 if (panelType == -1) 250 break; 251 252 struct lvds_bdb2 *lvds2; 253 struct lvds_bdb2_lfp_info *lvds2_lfp_info; 254 255 lvds2 = (struct lvds_bdb2 *)(vbios.memory + start); 256 lvds2_lfp_info = (struct lvds_bdb2_lfp_info *) 257 (vbios.memory + bdbOffset 258 + lvds2->panels[panelType].lfp_info_offset); 259 /* check terminator */ 260 if (lvds2_lfp_info->terminator != 0xffff) { 261 TRACE((DEVICE_NAME ": Incorrect LFP info terminator %x\n", 262 lvds2_lfp_info->terminator)); 263 #if 0 264 // FIXME the terminator is not present on my SandyBridge 265 // laptop, but the video mode is still correct. Maybe the 266 // format of the block has changed accross versions. We 267 // could check the size of the block to detect different 268 // layouts. 269 delete_area(vbios.area); 270 return false; 271 #endif 272 } 273 uint8_t* timing_data = vbios.memory + bdbOffset 274 + lvds2->panels[panelType].lfp_edid_dtd_offset; 275 TRACE((DEVICE_NAME ": found LFP of size %d x %d " 276 "in BIOS VBT tables\n", 277 lvds2_lfp_info->x_res, lvds2_lfp_info->y_res)); 278 279 vbios.timings_common.hsync_start = _H_ACTIVE(timing_data) 280 + _H_SYNC_OFF(timing_data); 281 vbios.timings_common.hsync_end 282 = vbios.timings_common.hsync_start 283 + _H_SYNC_WIDTH(timing_data); 284 vbios.timings_common.hsync_total = _H_ACTIVE(timing_data) 285 + _H_BLANK(timing_data); 286 vbios.timings_common.vsync_start = _V_ACTIVE(timing_data) 287 + _V_SYNC_OFF(timing_data); 288 vbios.timings_common.vsync_end 289 = vbios.timings_common.vsync_start 290 + _V_SYNC_WIDTH(timing_data); 291 vbios.timings_common.vsync_total = _V_ACTIVE(timing_data) 292 + _V_BLANK(timing_data); 293 294 vbios.shared_info = sharedInfo; 295 return feed_shared_info(timing_data); 296 } 297 } 298 } 299 300 delete_area(vbios.area); 301 return true; 302 } 303