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