Merge git://git.denx.de/u-boot-video
[oweals/u-boot.git] / lib / efi_loader / efi_gop.c
1 /*
2  *  EFI application disk support
3  *
4  *  Copyright (c) 2016 Alexander Graf
5  *
6  *  SPDX-License-Identifier:     GPL-2.0+
7  */
8
9 #include <common.h>
10 #include <dm.h>
11 #include <efi_loader.h>
12 #include <inttypes.h>
13 #include <lcd.h>
14 #include <malloc.h>
15 #include <video.h>
16
17 DECLARE_GLOBAL_DATA_PTR;
18
19 static const efi_guid_t efi_gop_guid = EFI_GOP_GUID;
20
21 struct efi_gop_obj {
22         /* Generic EFI object parent class data */
23         struct efi_object parent;
24         /* EFI Interface callback struct for gop */
25         struct efi_gop ops;
26         /* The only mode we support */
27         struct efi_gop_mode_info info;
28         struct efi_gop_mode mode;
29         /* Fields we only have acces to during init */
30         u32 bpix;
31         void *fb;
32 };
33
34 static efi_status_t EFIAPI gop_query_mode(struct efi_gop *this, u32 mode_number,
35                                           efi_uintn_t *size_of_info,
36                                           struct efi_gop_mode_info **info)
37 {
38         struct efi_gop_obj *gopobj;
39
40         EFI_ENTRY("%p, %x, %p, %p", this, mode_number, size_of_info, info);
41
42         gopobj = container_of(this, struct efi_gop_obj, ops);
43         *size_of_info = sizeof(gopobj->info);
44         *info = &gopobj->info;
45
46         return EFI_EXIT(EFI_SUCCESS);
47 }
48
49 static efi_status_t EFIAPI gop_set_mode(struct efi_gop *this, u32 mode_number)
50 {
51         EFI_ENTRY("%p, %x", this, mode_number);
52
53         if (mode_number != 0)
54                 return EFI_EXIT(EFI_INVALID_PARAMETER);
55
56         return EFI_EXIT(EFI_SUCCESS);
57 }
58
59 static __always_inline struct efi_gop_pixel efi_vid16_to_blt_col(u16 vid)
60 {
61         struct efi_gop_pixel blt = {
62                 .reserved = 0,
63         };
64
65         blt.blue  = (vid & 0x1f) << 3;
66         vid >>= 5;
67         blt.green = (vid & 0x3f) << 2;
68         vid >>= 6;
69         blt.red   = (vid & 0x1f) << 3;
70         return blt;
71 }
72
73 static __always_inline u16 efi_blt_col_to_vid16(struct efi_gop_pixel *blt)
74 {
75         return (u16)(blt->red   >> 3) << 11 |
76                (u16)(blt->green >> 2) <<  5 |
77                (u16)(blt->blue  >> 3);
78 }
79
80 static __always_inline efi_status_t gop_blt_int(struct efi_gop *this,
81                                                 struct efi_gop_pixel *bufferp,
82                                                 u32 operation, efi_uintn_t sx,
83                                                 efi_uintn_t sy, efi_uintn_t dx,
84                                                 efi_uintn_t dy,
85                                                 efi_uintn_t width,
86                                                 efi_uintn_t height,
87                                                 efi_uintn_t delta,
88                                                 efi_uintn_t vid_bpp)
89 {
90         struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
91         efi_uintn_t i, j, linelen, slineoff = 0, dlineoff, swidth, dwidth;
92         u32 *fb32 = gopobj->fb;
93         u16 *fb16 = gopobj->fb;
94         struct efi_gop_pixel *buffer = __builtin_assume_aligned(bufferp, 4);
95
96         if (delta) {
97                 /* Check for 4 byte alignment */
98                 if (delta & 3)
99                         return EFI_INVALID_PARAMETER;
100                 linelen = delta >> 2;
101         } else {
102                 linelen = width;
103         }
104
105         /* Check source rectangle */
106         switch (operation) {
107         case EFI_BLT_VIDEO_FILL:
108                 break;
109         case EFI_BLT_BUFFER_TO_VIDEO:
110                 if (sx + width > linelen)
111                         return EFI_INVALID_PARAMETER;
112                 break;
113         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
114         case EFI_BLT_VIDEO_TO_VIDEO:
115                 if (sx + width > gopobj->info.width ||
116                     sy + height > gopobj->info.height)
117                         return EFI_INVALID_PARAMETER;
118                 break;
119         default:
120                 return EFI_INVALID_PARAMETER;
121         }
122
123         /* Check destination rectangle */
124         switch (operation) {
125         case EFI_BLT_VIDEO_FILL:
126         case EFI_BLT_BUFFER_TO_VIDEO:
127         case EFI_BLT_VIDEO_TO_VIDEO:
128                 if (dx + width > gopobj->info.width ||
129                     dy + height > gopobj->info.height)
130                         return EFI_INVALID_PARAMETER;
131                 break;
132         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
133                 if (dx + width > linelen)
134                         return EFI_INVALID_PARAMETER;
135                 break;
136         }
137
138         /* Calculate line width */
139         switch (operation) {
140         case EFI_BLT_BUFFER_TO_VIDEO:
141                 swidth = linelen;
142                 break;
143         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
144         case EFI_BLT_VIDEO_TO_VIDEO:
145                 swidth = gopobj->info.width;
146                 if (!vid_bpp)
147                         return EFI_UNSUPPORTED;
148                 break;
149         case EFI_BLT_VIDEO_FILL:
150                 swidth = 0;
151                 break;
152         }
153
154         switch (operation) {
155         case EFI_BLT_BUFFER_TO_VIDEO:
156         case EFI_BLT_VIDEO_FILL:
157         case EFI_BLT_VIDEO_TO_VIDEO:
158                 dwidth = gopobj->info.width;
159                 if (!vid_bpp)
160                         return EFI_UNSUPPORTED;
161                 break;
162         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
163                 dwidth = linelen;
164                 break;
165         }
166
167         slineoff = swidth * sy;
168         dlineoff = dwidth * dy;
169         for (i = 0; i < height; i++) {
170                 for (j = 0; j < width; j++) {
171                         struct efi_gop_pixel pix;
172
173                         /* Read source pixel */
174                         switch (operation) {
175                         case EFI_BLT_VIDEO_FILL:
176                                 pix = *buffer;
177                                 break;
178                         case EFI_BLT_BUFFER_TO_VIDEO:
179                                 pix = buffer[slineoff + j + sx];
180                                 break;
181                         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
182                         case EFI_BLT_VIDEO_TO_VIDEO:
183                                 if (vid_bpp == 32)
184                                         pix = *(struct efi_gop_pixel *)&fb32[
185                                                 slineoff + j + sx];
186                                 else
187                                         pix = efi_vid16_to_blt_col(fb16[
188                                                 slineoff + j + sx]);
189                                 break;
190                         }
191
192                         /* Write destination pixel */
193                         switch (operation) {
194                         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
195                                 buffer[dlineoff + j + dx] = pix;
196                                 break;
197                         case EFI_BLT_BUFFER_TO_VIDEO:
198                         case EFI_BLT_VIDEO_FILL:
199                         case EFI_BLT_VIDEO_TO_VIDEO:
200                                 if (vid_bpp == 32)
201                                         fb32[dlineoff + j + dx] = *(u32 *)&pix;
202                                 else
203                                         fb16[dlineoff + j + dx] =
204                                                 efi_blt_col_to_vid16(&pix);
205                                 break;
206                         }
207                 }
208                 slineoff += swidth;
209                 dlineoff += dwidth;
210         }
211
212         return EFI_SUCCESS;
213 }
214
215 static efi_uintn_t gop_get_bpp(struct efi_gop *this)
216 {
217         struct efi_gop_obj *gopobj = container_of(this, struct efi_gop_obj, ops);
218         efi_uintn_t vid_bpp = 0;
219
220         switch (gopobj->bpix) {
221 #ifdef CONFIG_DM_VIDEO
222         case VIDEO_BPP32:
223 #else
224         case LCD_COLOR32:
225 #endif
226                 vid_bpp = 32;
227                 break;
228 #ifdef CONFIG_DM_VIDEO
229         case VIDEO_BPP16:
230 #else
231         case LCD_COLOR16:
232 #endif
233                 vid_bpp = 16;
234                 break;
235         }
236
237         return vid_bpp;
238 }
239
240 /*
241  * Gcc can't optimize our BLT function well, but we need to make sure that
242  * our 2-dimensional loop gets executed very quickly, otherwise the system
243  * will feel slow.
244  *
245  * By manually putting all obvious branch targets into functions which call
246  * our generic blt function with constants, the compiler can successfully
247  * optimize for speed.
248  */
249 static efi_status_t gop_blt_video_fill(struct efi_gop *this,
250                                        struct efi_gop_pixel *buffer,
251                                        u32 foo, efi_uintn_t sx,
252                                        efi_uintn_t sy, efi_uintn_t dx,
253                                        efi_uintn_t dy, efi_uintn_t width,
254                                        efi_uintn_t height, efi_uintn_t delta,
255                                        efi_uintn_t vid_bpp)
256 {
257         return gop_blt_int(this, buffer, EFI_BLT_VIDEO_FILL, sx, sy, dx,
258                            dy, width, height, delta, vid_bpp);
259 }
260
261 static efi_status_t gop_blt_buf_to_vid16(struct efi_gop *this,
262                                          struct efi_gop_pixel *buffer,
263                                          u32 foo, efi_uintn_t sx,
264                                          efi_uintn_t sy, efi_uintn_t dx,
265                                          efi_uintn_t dy, efi_uintn_t width,
266                                          efi_uintn_t height, efi_uintn_t delta)
267 {
268         return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
269                            dy, width, height, delta, 16);
270 }
271
272 static efi_status_t gop_blt_buf_to_vid32(struct efi_gop *this,
273                                          struct efi_gop_pixel *buffer,
274                                          u32 foo, efi_uintn_t sx,
275                                          efi_uintn_t sy, efi_uintn_t dx,
276                                          efi_uintn_t dy, efi_uintn_t width,
277                                          efi_uintn_t height, efi_uintn_t delta)
278 {
279         return gop_blt_int(this, buffer, EFI_BLT_BUFFER_TO_VIDEO, sx, sy, dx,
280                            dy, width, height, delta, 32);
281 }
282
283 static efi_status_t gop_blt_vid_to_vid(struct efi_gop *this,
284                                        struct efi_gop_pixel *buffer,
285                                        u32 foo, efi_uintn_t sx,
286                                        efi_uintn_t sy, efi_uintn_t dx,
287                                        efi_uintn_t dy, efi_uintn_t width,
288                                        efi_uintn_t height, efi_uintn_t delta,
289                                        efi_uintn_t vid_bpp)
290 {
291         return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_VIDEO, sx, sy, dx,
292                            dy, width, height, delta, vid_bpp);
293 }
294
295 static efi_status_t gop_blt_vid_to_buf(struct efi_gop *this,
296                                        struct efi_gop_pixel *buffer,
297                                        u32 foo, efi_uintn_t sx,
298                                        efi_uintn_t sy, efi_uintn_t dx,
299                                        efi_uintn_t dy, efi_uintn_t width,
300                                        efi_uintn_t height, efi_uintn_t delta,
301                                        efi_uintn_t vid_bpp)
302 {
303         return gop_blt_int(this, buffer, EFI_BLT_VIDEO_TO_BLT_BUFFER, sx, sy,
304                            dx, dy, width, height, delta, vid_bpp);
305 }
306
307 /*
308  * Copy rectangle.
309  *
310  * This function implements the Blt service of the EFI_GRAPHICS_OUTPUT_PROTOCOL.
311  * See the Unified Extensible Firmware Interface (UEFI) specification for
312  * details.
313  *
314  * @this:       EFI_GRAPHICS_OUTPUT_PROTOCOL
315  * @buffer:     pixel buffer
316  * @sx:         source x-coordinate
317  * @sy:         source y-coordinate
318  * @dx:         destination x-coordinate
319  * @dy:         destination y-coordinate
320  * @width:      width of rectangle
321  * @height:     height of rectangle
322  * @delta:      length in bytes of a line in the pixel buffer (optional)
323  * @return:     status code
324  */
325 efi_status_t EFIAPI gop_blt(struct efi_gop *this, struct efi_gop_pixel *buffer,
326                             u32 operation, efi_uintn_t sx,
327                             efi_uintn_t sy, efi_uintn_t dx,
328                             efi_uintn_t dy, efi_uintn_t width,
329                             efi_uintn_t height, efi_uintn_t delta)
330 {
331         efi_status_t ret = EFI_INVALID_PARAMETER;
332         efi_uintn_t vid_bpp;
333
334         EFI_ENTRY("%p, %p, %u, %zu, %zu, %zu, %zu, %zu, %zu, %zu", this,
335                   buffer, operation, sx, sy, dx, dy, width, height, delta);
336
337         vid_bpp = gop_get_bpp(this);
338
339         /* Allow for compiler optimization */
340         switch (operation) {
341         case EFI_BLT_VIDEO_FILL:
342                 ret = gop_blt_video_fill(this, buffer, operation, sx, sy, dx,
343                                          dy, width, height, delta, vid_bpp);
344                 break;
345         case EFI_BLT_BUFFER_TO_VIDEO:
346                 /* This needs to be super-fast, so duplicate for 16/32bpp */
347                 if (vid_bpp == 32)
348                         ret = gop_blt_buf_to_vid32(this, buffer, operation, sx,
349                                                    sy, dx, dy, width, height,
350                                                    delta);
351                 else
352                         ret = gop_blt_buf_to_vid16(this, buffer, operation, sx,
353                                                    sy, dx, dy, width, height,
354                                                    delta);
355                 break;
356         case EFI_BLT_VIDEO_TO_VIDEO:
357                 ret = gop_blt_vid_to_vid(this, buffer, operation, sx, sy, dx,
358                                          dy, width, height, delta, vid_bpp);
359                 break;
360         case EFI_BLT_VIDEO_TO_BLT_BUFFER:
361                 ret = gop_blt_vid_to_buf(this, buffer, operation, sx, sy, dx,
362                                          dy, width, height, delta, vid_bpp);
363                 break;
364         default:
365                 ret = EFI_UNSUPPORTED;
366         }
367
368         if (ret != EFI_SUCCESS)
369                 return EFI_EXIT(ret);
370
371 #ifdef CONFIG_DM_VIDEO
372         video_sync_all();
373 #else
374         lcd_sync();
375 #endif
376
377         return EFI_EXIT(EFI_SUCCESS);
378 }
379
380 /*
381  * Install graphical output protocol.
382  *
383  * If no supported video device exists this is not considered as an
384  * error.
385  */
386 efi_status_t efi_gop_register(void)
387 {
388         struct efi_gop_obj *gopobj;
389         u32 bpix, col, row;
390         u64 fb_base, fb_size;
391         void *fb;
392         efi_status_t ret;
393
394 #ifdef CONFIG_DM_VIDEO
395         struct udevice *vdev;
396         struct video_priv *priv;
397
398         /* We only support a single video output device for now */
399         if (uclass_first_device(UCLASS_VIDEO, &vdev) || !vdev) {
400                 debug("WARNING: No video device\n");
401                 return EFI_SUCCESS;
402         }
403
404         priv = dev_get_uclass_priv(vdev);
405         bpix = priv->bpix;
406         col = video_get_xsize(vdev);
407         row = video_get_ysize(vdev);
408         fb_base = (uintptr_t)priv->fb;
409         fb_size = priv->fb_size;
410         fb = priv->fb;
411 #else
412         int line_len;
413
414         bpix = panel_info.vl_bpix;
415         col = panel_info.vl_col;
416         row = panel_info.vl_row;
417         fb_base = gd->fb_base;
418         fb_size = lcd_get_size(&line_len);
419         fb = (void*)gd->fb_base;
420 #endif
421
422         switch (bpix) {
423 #ifdef CONFIG_DM_VIDEO
424         case VIDEO_BPP16:
425         case VIDEO_BPP32:
426 #else
427         case LCD_COLOR32:
428         case LCD_COLOR16:
429 #endif
430                 break;
431         default:
432                 /* So far, we only work in 16 or 32 bit mode */
433                 debug("WARNING: Unsupported video mode\n");
434                 return EFI_SUCCESS;
435         }
436
437         gopobj = calloc(1, sizeof(*gopobj));
438         if (!gopobj) {
439                 printf("ERROR: Out of memory\n");
440                 return EFI_OUT_OF_RESOURCES;
441         }
442
443         /* Hook up to the device list */
444         efi_add_handle(&gopobj->parent);
445
446         /* Fill in object data */
447         ret = efi_add_protocol(gopobj->parent.handle, &efi_gop_guid,
448                                &gopobj->ops);
449         if (ret != EFI_SUCCESS) {
450                 printf("ERROR: Failure adding gop protocol\n");
451                 return ret;
452         }
453         gopobj->ops.query_mode = gop_query_mode;
454         gopobj->ops.set_mode = gop_set_mode;
455         gopobj->ops.blt = gop_blt;
456         gopobj->ops.mode = &gopobj->mode;
457
458         gopobj->mode.max_mode = 1;
459         gopobj->mode.info = &gopobj->info;
460         gopobj->mode.info_size = sizeof(gopobj->info);
461
462 #ifdef CONFIG_DM_VIDEO
463         if (bpix == VIDEO_BPP32)
464 #else
465         if (bpix == LCD_COLOR32)
466 #endif
467         {
468                 /* With 32bit color space we can directly expose the fb */
469                 gopobj->mode.fb_base = fb_base;
470                 gopobj->mode.fb_size = fb_size;
471         }
472
473         gopobj->info.version = 0;
474         gopobj->info.width = col;
475         gopobj->info.height = row;
476         gopobj->info.pixel_format = EFI_GOT_RGBA8;
477         gopobj->info.pixels_per_scanline = col;
478
479         gopobj->bpix = bpix;
480         gopobj->fb = fb;
481
482         return EFI_SUCCESS;
483 }