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