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