1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* -*- linux-c -*- ------------------------------------------------------- * |
3 | * |
4 | * Copyright (C) 1991, 1992 Linus Torvalds |
5 | * Copyright 2007 rPath, Inc. - All Rights Reserved |
6 | * Copyright 2009 Intel Corporation; author H. Peter Anvin |
7 | * |
8 | * ----------------------------------------------------------------------- */ |
9 | |
10 | /* |
11 | * VESA text modes |
12 | */ |
13 | |
14 | #include "boot.h" |
15 | #include "video.h" |
16 | #include "vesa.h" |
17 | #include "string.h" |
18 | |
19 | /* VESA information */ |
20 | static struct vesa_general_info vginfo; |
21 | static struct vesa_mode_info vminfo; |
22 | |
23 | static __videocard video_vesa; |
24 | |
25 | #ifndef _WAKEUP |
26 | static void vesa_store_mode_params_graphics(void); |
27 | #else /* _WAKEUP */ |
28 | static inline void vesa_store_mode_params_graphics(void) {} |
29 | #endif /* _WAKEUP */ |
30 | |
31 | static int vesa_probe(void) |
32 | { |
33 | struct biosregs ireg, oreg; |
34 | u16 mode; |
35 | addr_t mode_ptr; |
36 | struct mode_info *mi; |
37 | int nmodes = 0; |
38 | |
39 | video_vesa.modes = GET_HEAP(struct mode_info, 0); |
40 | |
41 | initregs(regs: &ireg); |
42 | ireg.ax = 0x4f00; |
43 | ireg.di = (size_t)&vginfo; |
44 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
45 | |
46 | if (oreg.ax != 0x004f || |
47 | vginfo.signature != VESA_MAGIC || |
48 | vginfo.version < 0x0102) |
49 | return 0; /* Not present */ |
50 | |
51 | set_fs(vginfo.video_mode_ptr.seg); |
52 | mode_ptr = vginfo.video_mode_ptr.off; |
53 | |
54 | while ((mode = rdfs16(addr: mode_ptr)) != 0xffff) { |
55 | mode_ptr += 2; |
56 | |
57 | if (!heap_free(n: sizeof(struct mode_info))) |
58 | break; /* Heap full, can't save mode info */ |
59 | |
60 | if (mode & ~0x1ff) |
61 | continue; |
62 | |
63 | memset(&vminfo, 0, sizeof(vminfo)); /* Just in case... */ |
64 | |
65 | ireg.ax = 0x4f01; |
66 | ireg.cx = mode; |
67 | ireg.di = (size_t)&vminfo; |
68 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
69 | |
70 | if (oreg.ax != 0x004f) |
71 | continue; |
72 | |
73 | if ((vminfo.mode_attr & 0x15) == 0x05) { |
74 | /* Text Mode, TTY BIOS supported, |
75 | supported by hardware */ |
76 | mi = GET_HEAP(struct mode_info, 1); |
77 | mi->mode = mode + VIDEO_FIRST_VESA; |
78 | mi->depth = 0; /* text */ |
79 | mi->x = vminfo.h_res; |
80 | mi->y = vminfo.v_res; |
81 | nmodes++; |
82 | } else if ((vminfo.mode_attr & 0x99) == 0x99 && |
83 | (vminfo.memory_layout == 4 || |
84 | vminfo.memory_layout == 6) && |
85 | vminfo.memory_planes == 1) { |
86 | #ifdef CONFIG_BOOT_VESA_SUPPORT |
87 | /* Graphics mode, color, linear frame buffer |
88 | supported. Only register the mode if |
89 | if framebuffer is configured, however, |
90 | otherwise the user will be left without a screen. */ |
91 | mi = GET_HEAP(struct mode_info, 1); |
92 | mi->mode = mode + VIDEO_FIRST_VESA; |
93 | mi->depth = vminfo.bpp; |
94 | mi->x = vminfo.h_res; |
95 | mi->y = vminfo.v_res; |
96 | nmodes++; |
97 | #endif |
98 | } |
99 | } |
100 | |
101 | return nmodes; |
102 | } |
103 | |
104 | static int vesa_set_mode(struct mode_info *mode) |
105 | { |
106 | struct biosregs ireg, oreg; |
107 | int is_graphic; |
108 | u16 vesa_mode = mode->mode - VIDEO_FIRST_VESA; |
109 | |
110 | memset(&vminfo, 0, sizeof(vminfo)); /* Just in case... */ |
111 | |
112 | initregs(regs: &ireg); |
113 | ireg.ax = 0x4f01; |
114 | ireg.cx = vesa_mode; |
115 | ireg.di = (size_t)&vminfo; |
116 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
117 | |
118 | if (oreg.ax != 0x004f) |
119 | return -1; |
120 | |
121 | if ((vminfo.mode_attr & 0x15) == 0x05) { |
122 | /* It's a supported text mode */ |
123 | is_graphic = 0; |
124 | #ifdef CONFIG_BOOT_VESA_SUPPORT |
125 | } else if ((vminfo.mode_attr & 0x99) == 0x99) { |
126 | /* It's a graphics mode with linear frame buffer */ |
127 | is_graphic = 1; |
128 | vesa_mode |= 0x4000; /* Request linear frame buffer */ |
129 | #endif |
130 | } else { |
131 | return -1; /* Invalid mode */ |
132 | } |
133 | |
134 | |
135 | initregs(regs: &ireg); |
136 | ireg.ax = 0x4f02; |
137 | ireg.bx = vesa_mode; |
138 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
139 | |
140 | if (oreg.ax != 0x004f) |
141 | return -1; |
142 | |
143 | graphic_mode = is_graphic; |
144 | if (!is_graphic) { |
145 | /* Text mode */ |
146 | force_x = mode->x; |
147 | force_y = mode->y; |
148 | do_restore = 1; |
149 | } else { |
150 | /* Graphics mode */ |
151 | vesa_store_mode_params_graphics(); |
152 | } |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | |
158 | #ifndef _WAKEUP |
159 | |
160 | /* Switch DAC to 8-bit mode */ |
161 | static void vesa_dac_set_8bits(void) |
162 | { |
163 | struct biosregs ireg, oreg; |
164 | u8 dac_size = 6; |
165 | |
166 | /* If possible, switch the DAC to 8-bit mode */ |
167 | if (vginfo.capabilities & 1) { |
168 | initregs(regs: &ireg); |
169 | ireg.ax = 0x4f08; |
170 | ireg.bh = 0x08; |
171 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
172 | if (oreg.ax == 0x004f) |
173 | dac_size = oreg.bh; |
174 | } |
175 | |
176 | /* Set the color sizes to the DAC size, and offsets to 0 */ |
177 | boot_params.screen_info.red_size = dac_size; |
178 | boot_params.screen_info.green_size = dac_size; |
179 | boot_params.screen_info.blue_size = dac_size; |
180 | boot_params.screen_info.rsvd_size = dac_size; |
181 | |
182 | boot_params.screen_info.red_pos = 0; |
183 | boot_params.screen_info.green_pos = 0; |
184 | boot_params.screen_info.blue_pos = 0; |
185 | boot_params.screen_info.rsvd_pos = 0; |
186 | } |
187 | |
188 | /* Save the VESA protected mode info */ |
189 | static void vesa_store_pm_info(void) |
190 | { |
191 | struct biosregs ireg, oreg; |
192 | |
193 | initregs(regs: &ireg); |
194 | ireg.ax = 0x4f0a; |
195 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
196 | |
197 | if (oreg.ax != 0x004f) |
198 | return; |
199 | |
200 | boot_params.screen_info.vesapm_seg = oreg.es; |
201 | boot_params.screen_info.vesapm_off = oreg.di; |
202 | } |
203 | |
204 | /* |
205 | * Save video mode parameters for graphics mode |
206 | */ |
207 | static void vesa_store_mode_params_graphics(void) |
208 | { |
209 | /* Tell the kernel we're in VESA graphics mode */ |
210 | boot_params.screen_info.orig_video_isVGA = VIDEO_TYPE_VLFB; |
211 | |
212 | /* Mode parameters */ |
213 | boot_params.screen_info.vesa_attributes = vminfo.mode_attr; |
214 | boot_params.screen_info.lfb_linelength = vminfo.logical_scan; |
215 | boot_params.screen_info.lfb_width = vminfo.h_res; |
216 | boot_params.screen_info.lfb_height = vminfo.v_res; |
217 | boot_params.screen_info.lfb_depth = vminfo.bpp; |
218 | boot_params.screen_info.pages = vminfo.image_planes; |
219 | boot_params.screen_info.lfb_base = vminfo.lfb_ptr; |
220 | memcpy(&boot_params.screen_info.red_size, |
221 | &vminfo.rmask, 8); |
222 | |
223 | /* General parameters */ |
224 | boot_params.screen_info.lfb_size = vginfo.total_memory; |
225 | |
226 | if (vminfo.bpp <= 8) |
227 | vesa_dac_set_8bits(); |
228 | |
229 | vesa_store_pm_info(); |
230 | } |
231 | |
232 | /* |
233 | * Save EDID information for the kernel; this is invoked, separately, |
234 | * after mode-setting. |
235 | */ |
236 | void vesa_store_edid(void) |
237 | { |
238 | #ifdef CONFIG_FIRMWARE_EDID |
239 | struct biosregs ireg, oreg; |
240 | |
241 | /* Apparently used as a nonsense token... */ |
242 | memset(&boot_params.edid_info, 0x13, sizeof(boot_params.edid_info)); |
243 | |
244 | if (vginfo.version < 0x0200) |
245 | return; /* EDID requires VBE 2.0+ */ |
246 | |
247 | initregs(regs: &ireg); |
248 | ireg.ax = 0x4f15; /* VBE DDC */ |
249 | /* ireg.bx = 0x0000; */ /* Report DDC capabilities */ |
250 | /* ireg.cx = 0; */ /* Controller 0 */ |
251 | ireg.es = 0; /* ES:DI must be 0 by spec */ |
252 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
253 | |
254 | if (oreg.ax != 0x004f) |
255 | return; /* No EDID */ |
256 | |
257 | /* BH = time in seconds to transfer EDD information */ |
258 | /* BL = DDC level supported */ |
259 | |
260 | ireg.ax = 0x4f15; /* VBE DDC */ |
261 | ireg.bx = 0x0001; /* Read EDID */ |
262 | /* ireg.cx = 0; */ /* Controller 0 */ |
263 | /* ireg.dx = 0; */ /* EDID block number */ |
264 | ireg.es = ds(); |
265 | ireg.di =(size_t)&boot_params.edid_info; /* (ES:)Pointer to block */ |
266 | intcall(int_no: 0x10, ireg: &ireg, oreg: &oreg); |
267 | #endif /* CONFIG_FIRMWARE_EDID */ |
268 | } |
269 | |
270 | #endif /* not _WAKEUP */ |
271 | |
272 | static __videocard video_vesa = |
273 | { |
274 | .card_name = "VESA" , |
275 | .probe = vesa_probe, |
276 | .set_mode = vesa_set_mode, |
277 | .xmode_first = VIDEO_FIRST_VESA, |
278 | .xmode_n = 0x200, |
279 | }; |
280 | |