xref: /haiku/src/add-ons/kernel/drivers/graphics/intel_extreme/bios.cpp (revision 0c66734e4da3cdd60a0552014b0f46275a358796)
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; /* unapproved */
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 /* TODO: move code to accelerant, if possible */
123 
124 /*!	This is reimplementation, Haiku uses BIOS call and gets most current panel
125 	info, we're, otherwise, digging in VBIOS memory and parsing VBT tables to
126 	get native panel timings. This will allow to get non-updated,
127 	PROM-programmed timings info when compensation mode is off on your machine.
128 */
129 static bool
130 get_bios(void)
131 {
132 	static const uint64_t kVBIOSAddress = 0xc0000;
133 	static const int kVBIOSSize = 64 * 1024;
134 		// FIXME: is this the same for all cards?
135 
136 	/* !!!DANGER!!!: mapping of BIOS using legacy location for now,
137 	hence, if panel mode will be set using info from VBT, it will
138 	be taken from primary card's VBIOS */
139 	vbios.area = map_physical_memory("VBIOS mapping", kVBIOSAddress,
140 		kVBIOSSize, B_ANY_KERNEL_ADDRESS, B_READ_AREA, (void**)&vbios.memory);
141 
142 	if (vbios.area < 0)
143 		return false;
144 
145 	TRACE((DEVICE_NAME ": mapping VBIOS: 0x%" B_PRIx64 " -> %p\n",
146 		kVBIOSAddress, vbios.memory));
147 
148 	int vbtOffset = vbios.ReadWord(kVbtPointer);
149 	if (vbtOffset >= kVBIOSSize) {
150 		TRACE((DEVICE_NAME": bad VBT offset : 0x%x\n", vbtOffset));
151 		delete_area(vbios.area);
152 		return false;
153 	}
154 
155 	struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset);
156 	if (memcmp(vbt->signature, "$VBT", 4) != 0) {
157 		TRACE((DEVICE_NAME": bad VBT signature: %20s\n", vbt->signature));
158 		delete_area(vbios.area);
159 		return false;
160 	}
161 
162 	return true;
163 }
164 
165 
166 static bool
167 feed_shared_info(uint8* data)
168 {
169 	bool bogus = false;
170 
171 	/* handle bogus h/vtotal values, if got such */
172 	if (vbios.timings_common.hsync_end > vbios.timings_common.hsync_total) {
173 		vbios.timings_common.hsync_total = vbios.timings_common.hsync_end + 1;
174 		bogus = true;
175 		TRACE((DEVICE_NAME": got bogus htotal. Fixing\n"));
176 	}
177 	if (vbios.timings_common.vsync_end > vbios.timings_common.vsync_total) {
178 		vbios.timings_common.vsync_total = vbios.timings_common.vsync_end + 1;
179 		bogus = true;
180 		TRACE((DEVICE_NAME": got bogus vtotal. Fixing\n"));
181 	}
182 
183 	if (bogus) {
184 		TRACE((DEVICE_NAME": adjusted LFP modeline: x%d Hz,\t"
185 			"%d %d %d %d   %d %d %d %d\n",
186 			_PIXEL_CLOCK(data) / ((_H_ACTIVE(data) + _H_BLANK(data))
187 				* (_V_ACTIVE(data) + _V_BLANK(data))),
188 			_H_ACTIVE(data), vbios.timings_common.hsync_start,
189 			vbios.timings_common.hsync_end, vbios.timings_common.hsync_total,
190 			_V_ACTIVE(data), vbios.timings_common.vsync_start,
191 			vbios.timings_common.vsync_end, vbios.timings_common.vsync_total));
192 	}
193 
194 	/* TODO: add retrieved info to edid info struct, not fixed mode struct */
195 
196 	/* struct display_timing is not packed, so we have to set elements
197 	individually */
198 	vbios.shared_info->timing.pixel_clock = _PIXEL_CLOCK(data) / 1000;
199 	vbios.shared_info->timing.h_display = vbios.shared_info->virtual_width
200 		= _H_ACTIVE(data);
201 	vbios.shared_info->timing.h_sync_start = vbios.timings_common.hsync_start;
202 	vbios.shared_info->timing.h_sync_end = vbios.timings_common.hsync_end;
203 	vbios.shared_info->timing.h_total = vbios.timings_common.hsync_total;
204 	vbios.shared_info->timing.v_display = vbios.shared_info->virtual_height
205 		= _V_ACTIVE(data);
206 	vbios.shared_info->timing.v_sync_start = vbios.timings_common.vsync_start;
207 	vbios.shared_info->timing.v_sync_end = vbios.timings_common.vsync_end;
208 	vbios.shared_info->timing.v_total = vbios.timings_common.vsync_total;
209 
210 	delete_area(vbios.area);
211 	return true;
212 }
213 
214 
215 bool
216 get_lvds_mode_from_bios(display_mode* sharedInfo)
217 {
218 	if (!get_bios())
219 		return false;
220 
221 	int vbtOffset = vbios.ReadWord(kVbtPointer);
222 	struct vbt_header* vbt = (struct vbt_header*)(vbios.memory + vbtOffset);
223 	int bdbOffset = vbtOffset + vbt->bdb_offset;
224 
225 	struct bdb_header* bdb = (struct bdb_header*)(vbios.memory + bdbOffset);
226 	if (memcmp(bdb->signature, "BIOS_DATA_BLOCK ", 16) != 0) {
227 		TRACE((DEVICE_NAME": bad BDB signature\n"));
228 		delete_area(vbios.area);
229 	}
230 
231 	TRACE((DEVICE_NAME": parsing BDB blocks\n"));
232 	int blockSize;
233 	int panelType = -1;
234 
235 	for (int bdbBlockOffset = bdb->header_size; bdbBlockOffset < bdb->bdb_size;
236 			bdbBlockOffset += blockSize) {
237 		int start = bdbOffset + bdbBlockOffset;
238 
239 		int id = vbios.memory[start];
240 		blockSize = vbios.ReadWord(start + 1) + 3;
241 		// TRACE((DEVICE_NAME": found BDB block type %d\n", id));
242 		switch (id) {
243 			case 40: // FIXME magic numbers
244 			{
245 				struct lvds_bdb1 *lvds1;
246 				lvds1 = (struct lvds_bdb1 *)(vbios.memory + start);
247 				panelType = lvds1->panel_type;
248 				break;
249 			}
250 			case 41:
251 			{
252 				if (panelType == -1)
253 					break;
254 				struct lvds_bdb2 *lvds2;
255 				struct lvds_bdb2_lfp_info *lvds2_lfp_info;
256 
257 				lvds2 = (struct lvds_bdb2 *)(vbios.memory + start);
258 				lvds2_lfp_info = (struct lvds_bdb2_lfp_info *)
259 					(vbios.memory + bdbOffset
260 					+ lvds2->panels[panelType].lfp_info_offset);
261 				/* found bad one terminator */
262 				if (lvds2_lfp_info->terminator != 0xffff) {
263 					delete_area(vbios.area);
264 					return false;
265 				}
266 				uint8_t* timing_data = vbios.memory + bdbOffset
267 					+ lvds2->panels[panelType].lfp_edid_dtd_offset;
268 				TRACE((DEVICE_NAME": found LFP of size %d x %d "
269 					"in BIOS VBT tables\n",
270 					lvds2_lfp_info->x_res, lvds2_lfp_info->y_res));
271 
272 				vbios.timings_common.hsync_start = _H_ACTIVE(timing_data)
273 					+ _H_SYNC_OFF(timing_data);
274 				vbios.timings_common.hsync_end
275 					= vbios.timings_common.hsync_start
276 					+ _H_SYNC_WIDTH(timing_data);
277 				vbios.timings_common.hsync_total = _H_ACTIVE(timing_data)
278 					+ _H_BLANK(timing_data);
279 				vbios.timings_common.vsync_start = _V_ACTIVE(timing_data)
280 					+ _V_SYNC_OFF(timing_data);
281 				vbios.timings_common.vsync_end
282 					= vbios.timings_common.vsync_start
283 					+ _V_SYNC_WIDTH(timing_data);
284 				vbios.timings_common.vsync_total = _V_ACTIVE(timing_data)
285 					+ _V_BLANK(timing_data);
286 
287 				vbios.shared_info = sharedInfo;
288 				return feed_shared_info(timing_data);
289 			}
290 		}
291 	}
292 
293 	delete_area(vbios.area);
294 	return true;
295 }
296