xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
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