1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Framebuffer driver for EFI/UEFI based system |
4 | * |
5 | * (c) 2006 Edgar Hucek <gimli@dark-green.com> |
6 | * Original efi driver written by Gerd Knorr <kraxel@goldbach.in-berlin.de> |
7 | * |
8 | */ |
9 | |
10 | #include <linux/aperture.h> |
11 | #include <linux/kernel.h> |
12 | #include <linux/efi.h> |
13 | #include <linux/efi-bgrt.h> |
14 | #include <linux/errno.h> |
15 | #include <linux/fb.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/printk.h> |
18 | #include <linux/screen_info.h> |
19 | #include <video/vga.h> |
20 | #include <asm/efi.h> |
21 | #include <drm/drm_utils.h> /* For drm_get_panel_orientation_quirk */ |
22 | #include <drm/drm_connector.h> /* For DRM_MODE_PANEL_ORIENTATION_* */ |
23 | |
24 | struct { |
25 | u16 ; |
26 | u32 ; |
27 | u32 ; |
28 | u32 ; |
29 | } __packed; |
30 | |
31 | struct { |
32 | u32 ; |
33 | s32 ; |
34 | s32 ; |
35 | u16 ; |
36 | u16 ; |
37 | u32 ; |
38 | u32 ; |
39 | u32 ; |
40 | u32 ; |
41 | u32 ; |
42 | u32 ; |
43 | } __packed; |
44 | |
45 | static bool use_bgrt = true; |
46 | static bool request_mem_succeeded = false; |
47 | static u64 mem_flags = EFI_MEMORY_WC | EFI_MEMORY_UC; |
48 | |
49 | struct efifb_par { |
50 | u32 pseudo_palette[16]; |
51 | resource_size_t base; |
52 | resource_size_t size; |
53 | }; |
54 | |
55 | static struct fb_var_screeninfo efifb_defined = { |
56 | .activate = FB_ACTIVATE_NOW, |
57 | .height = -1, |
58 | .width = -1, |
59 | .right_margin = 32, |
60 | .upper_margin = 16, |
61 | .lower_margin = 4, |
62 | .vsync_len = 4, |
63 | .vmode = FB_VMODE_NONINTERLACED, |
64 | }; |
65 | |
66 | static struct fb_fix_screeninfo efifb_fix = { |
67 | .id = "EFI VGA" , |
68 | .type = FB_TYPE_PACKED_PIXELS, |
69 | .accel = FB_ACCEL_NONE, |
70 | .visual = FB_VISUAL_TRUECOLOR, |
71 | }; |
72 | |
73 | static int efifb_setcolreg(unsigned regno, unsigned red, unsigned green, |
74 | unsigned blue, unsigned transp, |
75 | struct fb_info *info) |
76 | { |
77 | /* |
78 | * Set a single color register. The values supplied are |
79 | * already rounded down to the hardware's capabilities |
80 | * (according to the entries in the `var' structure). Return |
81 | * != 0 for invalid regno. |
82 | */ |
83 | |
84 | if (regno >= info->cmap.len) |
85 | return 1; |
86 | |
87 | if (regno < 16) { |
88 | red >>= 16 - info->var.red.length; |
89 | green >>= 16 - info->var.green.length; |
90 | blue >>= 16 - info->var.blue.length; |
91 | ((u32 *)(info->pseudo_palette))[regno] = |
92 | (red << info->var.red.offset) | |
93 | (green << info->var.green.offset) | |
94 | (blue << info->var.blue.offset); |
95 | } |
96 | return 0; |
97 | } |
98 | |
99 | /* |
100 | * If fbcon deffered console takeover is configured, the intent is for the |
101 | * framebuffer to show the boot graphics (e.g. vendor logo) until there is some |
102 | * (error) message to display. But the boot graphics may have been destroyed by |
103 | * e.g. option ROM output, detect this and restore the boot graphics. |
104 | */ |
105 | #if defined CONFIG_FRAMEBUFFER_CONSOLE_DEFERRED_TAKEOVER && \ |
106 | defined CONFIG_ACPI_BGRT |
107 | static void efifb_copy_bmp(u8 *src, u32 *dst, int width, const struct screen_info *si) |
108 | { |
109 | u8 r, g, b; |
110 | |
111 | while (width--) { |
112 | b = *src++; |
113 | g = *src++; |
114 | r = *src++; |
115 | *dst++ = (r << si->red_pos) | |
116 | (g << si->green_pos) | |
117 | (b << si->blue_pos); |
118 | } |
119 | } |
120 | |
121 | #ifdef CONFIG_X86 |
122 | /* |
123 | * On x86 some firmwares use a low non native resolution for the display when |
124 | * they have shown some text messages. While keeping the bgrt filled with info |
125 | * for the native resolution. If the bgrt image intended for the native |
126 | * resolution still fits, it will be displayed very close to the right edge of |
127 | * the display looking quite bad. This function checks for this. |
128 | */ |
129 | static bool efifb_bgrt_sanity_check(const struct screen_info *si, u32 bmp_width) |
130 | { |
131 | /* |
132 | * All x86 firmwares horizontally center the image (the yoffset |
133 | * calculations differ between boards, but xoffset is predictable). |
134 | */ |
135 | u32 expected_xoffset = (si->lfb_width - bmp_width) / 2; |
136 | |
137 | return bgrt_tab.image_offset_x == expected_xoffset; |
138 | } |
139 | #else |
140 | static bool efifb_bgrt_sanity_check(const struct screen_info *si, u32 bmp_width) |
141 | { |
142 | return true; |
143 | } |
144 | #endif |
145 | |
146 | static void efifb_show_boot_graphics(struct fb_info *info, const struct screen_info *si) |
147 | { |
148 | u32 bmp_width, bmp_height, bmp_pitch, dst_x, y, src_y; |
149 | struct bmp_file_header *; |
150 | struct bmp_dib_header *; |
151 | void *bgrt_image = NULL; |
152 | u8 *dst = info->screen_base; |
153 | |
154 | if (!use_bgrt) |
155 | return; |
156 | |
157 | if (!bgrt_tab.image_address) { |
158 | pr_info("efifb: No BGRT, not showing boot graphics\n" ); |
159 | return; |
160 | } |
161 | |
162 | if (bgrt_tab.status & 0x06) { |
163 | pr_info("efifb: BGRT rotation bits set, not showing boot graphics\n" ); |
164 | return; |
165 | } |
166 | |
167 | /* Avoid flashing the logo if we're going to print std probe messages */ |
168 | if (console_loglevel > CONSOLE_LOGLEVEL_QUIET) |
169 | return; |
170 | |
171 | /* bgrt_tab.status is unreliable, so we don't check it */ |
172 | |
173 | if (si->lfb_depth != 32) { |
174 | pr_info("efifb: not 32 bits, not showing boot graphics\n" ); |
175 | return; |
176 | } |
177 | |
178 | bgrt_image = memremap(offset: bgrt_tab.image_address, size: bgrt_image_size, |
179 | flags: MEMREMAP_WB); |
180 | if (!bgrt_image) { |
181 | pr_warn("efifb: Ignoring BGRT: failed to map image memory\n" ); |
182 | return; |
183 | } |
184 | |
185 | if (bgrt_image_size < (sizeof(*file_header) + sizeof(*dib_header))) |
186 | goto error; |
187 | |
188 | file_header = bgrt_image; |
189 | if (file_header->id != 0x4d42 || file_header->reserved != 0) |
190 | goto error; |
191 | |
192 | dib_header = bgrt_image + sizeof(*file_header); |
193 | if (dib_header->dib_header_size != 40 || dib_header->width < 0 || |
194 | dib_header->planes != 1 || dib_header->bpp != 24 || |
195 | dib_header->compression != 0) |
196 | goto error; |
197 | |
198 | bmp_width = dib_header->width; |
199 | bmp_height = abs(dib_header->height); |
200 | bmp_pitch = round_up(3 * bmp_width, 4); |
201 | |
202 | if ((file_header->bitmap_offset + bmp_pitch * bmp_height) > |
203 | bgrt_image_size) |
204 | goto error; |
205 | |
206 | if ((bgrt_tab.image_offset_x + bmp_width) > si->lfb_width || |
207 | (bgrt_tab.image_offset_y + bmp_height) > si->lfb_height) |
208 | goto error; |
209 | |
210 | if (!efifb_bgrt_sanity_check(si, bmp_width)) |
211 | goto error; |
212 | |
213 | pr_info("efifb: showing boot graphics\n" ); |
214 | |
215 | for (y = 0; y < si->lfb_height; y++, dst += si->lfb_linelength) { |
216 | /* Only background? */ |
217 | if (y < bgrt_tab.image_offset_y || |
218 | y >= (bgrt_tab.image_offset_y + bmp_height)) { |
219 | memset(dst, 0, 4 * si->lfb_width); |
220 | continue; |
221 | } |
222 | |
223 | src_y = y - bgrt_tab.image_offset_y; |
224 | /* Positive header height means upside down row order */ |
225 | if (dib_header->height > 0) |
226 | src_y = (bmp_height - 1) - src_y; |
227 | |
228 | memset(dst, 0, bgrt_tab.image_offset_x * 4); |
229 | dst_x = bgrt_tab.image_offset_x; |
230 | efifb_copy_bmp(src: bgrt_image + file_header->bitmap_offset + |
231 | src_y * bmp_pitch, |
232 | dst: (u32 *)dst + dst_x, width: bmp_width, si); |
233 | dst_x += bmp_width; |
234 | memset((u32 *)dst + dst_x, 0, (si->lfb_width - dst_x) * 4); |
235 | } |
236 | |
237 | memunmap(addr: bgrt_image); |
238 | return; |
239 | |
240 | error: |
241 | memunmap(addr: bgrt_image); |
242 | pr_warn("efifb: Ignoring BGRT: unexpected or invalid BMP data\n" ); |
243 | } |
244 | #else |
245 | static inline void efifb_show_boot_graphics(struct fb_info *info, const struct screen_info *si) |
246 | { } |
247 | #endif |
248 | |
249 | /* |
250 | * fb_ops.fb_destroy is called by the last put_fb_info() call at the end |
251 | * of unregister_framebuffer() or fb_release(). Do any cleanup here. |
252 | */ |
253 | static void efifb_destroy(struct fb_info *info) |
254 | { |
255 | struct efifb_par *par = info->par; |
256 | |
257 | if (info->screen_base) { |
258 | if (mem_flags & (EFI_MEMORY_UC | EFI_MEMORY_WC)) |
259 | iounmap(addr: info->screen_base); |
260 | else |
261 | memunmap(addr: info->screen_base); |
262 | } |
263 | |
264 | if (request_mem_succeeded) |
265 | release_mem_region(par->base, par->size); |
266 | fb_dealloc_cmap(cmap: &info->cmap); |
267 | |
268 | framebuffer_release(info); |
269 | } |
270 | |
271 | static const struct fb_ops efifb_ops = { |
272 | .owner = THIS_MODULE, |
273 | FB_DEFAULT_IOMEM_OPS, |
274 | .fb_destroy = efifb_destroy, |
275 | .fb_setcolreg = efifb_setcolreg, |
276 | }; |
277 | |
278 | static int efifb_setup(struct screen_info *si, char *options) |
279 | { |
280 | char *this_opt; |
281 | |
282 | if (options && *options) { |
283 | while ((this_opt = strsep(&options, "," )) != NULL) { |
284 | if (!*this_opt) continue; |
285 | |
286 | efifb_setup_from_dmi(si, opt: this_opt); |
287 | |
288 | if (!strncmp(this_opt, "base:" , 5)) |
289 | si->lfb_base = simple_strtoul(this_opt+5, NULL, 0); |
290 | else if (!strncmp(this_opt, "stride:" , 7)) |
291 | si->lfb_linelength = simple_strtoul(this_opt+7, NULL, 0) * 4; |
292 | else if (!strncmp(this_opt, "height:" , 7)) |
293 | si->lfb_height = simple_strtoul(this_opt+7, NULL, 0); |
294 | else if (!strncmp(this_opt, "width:" , 6)) |
295 | si->lfb_width = simple_strtoul(this_opt+6, NULL, 0); |
296 | else if (!strcmp(this_opt, "nowc" )) |
297 | mem_flags &= ~EFI_MEMORY_WC; |
298 | else if (!strcmp(this_opt, "nobgrt" )) |
299 | use_bgrt = false; |
300 | } |
301 | } |
302 | |
303 | return 0; |
304 | } |
305 | |
306 | static inline bool fb_base_is_valid(struct screen_info *si) |
307 | { |
308 | if (si->lfb_base) |
309 | return true; |
310 | |
311 | if (!(si->capabilities & VIDEO_CAPABILITY_64BIT_BASE)) |
312 | return false; |
313 | |
314 | if (si->ext_lfb_base) |
315 | return true; |
316 | |
317 | return false; |
318 | } |
319 | |
320 | #define efifb_attr_decl(name, fmt) \ |
321 | static ssize_t name##_show(struct device *dev, \ |
322 | struct device_attribute *attr, \ |
323 | char *buf) \ |
324 | { \ |
325 | struct screen_info *si = dev_get_platdata(dev); \ |
326 | if (!si) \ |
327 | return -ENODEV; \ |
328 | return sprintf(buf, fmt "\n", (si->lfb_##name)); \ |
329 | } \ |
330 | static DEVICE_ATTR_RO(name) |
331 | |
332 | efifb_attr_decl(base, "0x%x" ); |
333 | efifb_attr_decl(linelength, "%u" ); |
334 | efifb_attr_decl(height, "%u" ); |
335 | efifb_attr_decl(width, "%u" ); |
336 | efifb_attr_decl(depth, "%u" ); |
337 | |
338 | static struct attribute *efifb_attrs[] = { |
339 | &dev_attr_base.attr, |
340 | &dev_attr_linelength.attr, |
341 | &dev_attr_width.attr, |
342 | &dev_attr_height.attr, |
343 | &dev_attr_depth.attr, |
344 | NULL |
345 | }; |
346 | ATTRIBUTE_GROUPS(efifb); |
347 | |
348 | static int efifb_probe(struct platform_device *dev) |
349 | { |
350 | struct screen_info *si; |
351 | struct fb_info *info; |
352 | struct efifb_par *par; |
353 | int err, orientation; |
354 | unsigned int size_vmode; |
355 | unsigned int size_remap; |
356 | unsigned int size_total; |
357 | char *option = NULL; |
358 | efi_memory_desc_t md; |
359 | |
360 | /* |
361 | * If we fail probing the device, the kernel might try a different |
362 | * driver. We get a copy of the attached screen_info, so that we can |
363 | * modify its values without affecting later drivers. |
364 | */ |
365 | si = dev_get_platdata(dev: &dev->dev); |
366 | if (!si) |
367 | return -ENODEV; |
368 | si = devm_kmemdup(dev: &dev->dev, src: si, len: sizeof(*si), GFP_KERNEL); |
369 | if (!si) |
370 | return -ENOMEM; |
371 | |
372 | if (si->orig_video_isVGA != VIDEO_TYPE_EFI) |
373 | return -ENODEV; |
374 | |
375 | if (fb_get_options(name: "efifb" , option: &option)) |
376 | return -ENODEV; |
377 | efifb_setup(si, options: option); |
378 | |
379 | /* We don't get linelength from UGA Draw Protocol, only from |
380 | * EFI Graphics Protocol. So if it's not in DMI, and it's not |
381 | * passed in from the user, we really can't use the framebuffer. |
382 | */ |
383 | if (!si->lfb_linelength) |
384 | return -ENODEV; |
385 | |
386 | if (!si->lfb_depth) |
387 | si->lfb_depth = 32; |
388 | if (!si->pages) |
389 | si->pages = 1; |
390 | if (!fb_base_is_valid(si)) { |
391 | printk(KERN_DEBUG "efifb: invalid framebuffer address\n" ); |
392 | return -ENODEV; |
393 | } |
394 | printk(KERN_INFO "efifb: probing for efifb\n" ); |
395 | |
396 | /* just assume they're all unset if any are */ |
397 | if (!si->blue_size) { |
398 | si->blue_size = 8; |
399 | si->blue_pos = 0; |
400 | si->green_size = 8; |
401 | si->green_pos = 8; |
402 | si->red_size = 8; |
403 | si->red_pos = 16; |
404 | si->rsvd_size = 8; |
405 | si->rsvd_pos = 24; |
406 | } |
407 | |
408 | efifb_fix.smem_start = __screen_info_lfb_base(si); |
409 | |
410 | efifb_defined.bits_per_pixel = si->lfb_depth; |
411 | efifb_defined.xres = si->lfb_width; |
412 | efifb_defined.yres = si->lfb_height; |
413 | efifb_fix.line_length = si->lfb_linelength; |
414 | |
415 | /* size_vmode -- that is the amount of memory needed for the |
416 | * used video mode, i.e. the minimum amount of |
417 | * memory we need. */ |
418 | size_vmode = efifb_defined.yres * efifb_fix.line_length; |
419 | |
420 | /* size_total -- all video memory we have. Used for |
421 | * entries, ressource allocation and bounds |
422 | * checking. */ |
423 | size_total = si->lfb_size; |
424 | if (size_total < size_vmode) |
425 | size_total = size_vmode; |
426 | |
427 | /* size_remap -- the amount of video memory we are going to |
428 | * use for efifb. With modern cards it is no |
429 | * option to simply use size_total as that |
430 | * wastes plenty of kernel address space. */ |
431 | size_remap = size_vmode * 2; |
432 | if (size_remap > size_total) |
433 | size_remap = size_total; |
434 | if (size_remap % PAGE_SIZE) |
435 | size_remap += PAGE_SIZE - (size_remap % PAGE_SIZE); |
436 | efifb_fix.smem_len = size_remap; |
437 | |
438 | if (request_mem_region(efifb_fix.smem_start, size_remap, "efifb" )) { |
439 | request_mem_succeeded = true; |
440 | } else { |
441 | /* We cannot make this fatal. Sometimes this comes from magic |
442 | spaces our resource handlers simply don't know about */ |
443 | pr_warn("efifb: cannot reserve video memory at 0x%lx\n" , |
444 | efifb_fix.smem_start); |
445 | } |
446 | |
447 | info = framebuffer_alloc(size: sizeof(*par), dev: &dev->dev); |
448 | if (!info) { |
449 | err = -ENOMEM; |
450 | goto err_release_mem; |
451 | } |
452 | platform_set_drvdata(pdev: dev, data: info); |
453 | par = info->par; |
454 | info->pseudo_palette = par->pseudo_palette; |
455 | |
456 | par->base = efifb_fix.smem_start; |
457 | par->size = size_remap; |
458 | |
459 | if (efi_enabled(EFI_MEMMAP) && |
460 | !efi_mem_desc_lookup(phys_addr: efifb_fix.smem_start, out_md: &md)) { |
461 | if ((efifb_fix.smem_start + efifb_fix.smem_len) > |
462 | (md.phys_addr + (md.num_pages << EFI_PAGE_SHIFT))) { |
463 | pr_err("efifb: video memory @ 0x%lx spans multiple EFI memory regions\n" , |
464 | efifb_fix.smem_start); |
465 | err = -EIO; |
466 | goto err_release_fb; |
467 | } |
468 | /* |
469 | * If the UEFI memory map covers the efifb region, we may only |
470 | * remap it using the attributes the memory map prescribes. |
471 | */ |
472 | md.attribute &= EFI_MEMORY_UC | EFI_MEMORY_WC | |
473 | EFI_MEMORY_WT | EFI_MEMORY_WB; |
474 | if (md.attribute) { |
475 | mem_flags |= EFI_MEMORY_WT | EFI_MEMORY_WB; |
476 | mem_flags &= md.attribute; |
477 | } |
478 | } |
479 | if (mem_flags & EFI_MEMORY_WC) |
480 | info->screen_base = ioremap_wc(offset: efifb_fix.smem_start, |
481 | size: efifb_fix.smem_len); |
482 | else if (mem_flags & EFI_MEMORY_UC) |
483 | info->screen_base = ioremap(offset: efifb_fix.smem_start, |
484 | size: efifb_fix.smem_len); |
485 | else if (mem_flags & EFI_MEMORY_WT) |
486 | info->screen_base = memremap(offset: efifb_fix.smem_start, |
487 | size: efifb_fix.smem_len, flags: MEMREMAP_WT); |
488 | else if (mem_flags & EFI_MEMORY_WB) |
489 | info->screen_base = memremap(offset: efifb_fix.smem_start, |
490 | size: efifb_fix.smem_len, flags: MEMREMAP_WB); |
491 | if (!info->screen_base) { |
492 | pr_err("efifb: abort, cannot remap video memory 0x%x @ 0x%lx\n" , |
493 | efifb_fix.smem_len, efifb_fix.smem_start); |
494 | err = -EIO; |
495 | goto err_release_fb; |
496 | } |
497 | |
498 | efifb_show_boot_graphics(info, si); |
499 | |
500 | pr_info("efifb: framebuffer at 0x%lx, using %dk, total %dk\n" , |
501 | efifb_fix.smem_start, size_remap/1024, size_total/1024); |
502 | pr_info("efifb: mode is %dx%dx%d, linelength=%d, pages=%d\n" , |
503 | efifb_defined.xres, efifb_defined.yres, |
504 | efifb_defined.bits_per_pixel, efifb_fix.line_length, |
505 | si->pages); |
506 | |
507 | efifb_defined.xres_virtual = efifb_defined.xres; |
508 | efifb_defined.yres_virtual = efifb_fix.smem_len / |
509 | efifb_fix.line_length; |
510 | pr_info("efifb: scrolling: redraw\n" ); |
511 | efifb_defined.yres_virtual = efifb_defined.yres; |
512 | |
513 | /* some dummy values for timing to make fbset happy */ |
514 | efifb_defined.pixclock = 10000000 / efifb_defined.xres * |
515 | 1000 / efifb_defined.yres; |
516 | efifb_defined.left_margin = (efifb_defined.xres / 8) & 0xf8; |
517 | efifb_defined.hsync_len = (efifb_defined.xres / 8) & 0xf8; |
518 | |
519 | efifb_defined.red.offset = si->red_pos; |
520 | efifb_defined.red.length = si->red_size; |
521 | efifb_defined.green.offset = si->green_pos; |
522 | efifb_defined.green.length = si->green_size; |
523 | efifb_defined.blue.offset = si->blue_pos; |
524 | efifb_defined.blue.length = si->blue_size; |
525 | efifb_defined.transp.offset = si->rsvd_pos; |
526 | efifb_defined.transp.length = si->rsvd_size; |
527 | |
528 | pr_info("efifb: %s: " |
529 | "size=%d:%d:%d:%d, shift=%d:%d:%d:%d\n" , |
530 | "Truecolor" , |
531 | si->rsvd_size, |
532 | si->red_size, |
533 | si->green_size, |
534 | si->blue_size, |
535 | si->rsvd_pos, |
536 | si->red_pos, |
537 | si->green_pos, |
538 | si->blue_pos); |
539 | |
540 | efifb_fix.ypanstep = 0; |
541 | efifb_fix.ywrapstep = 0; |
542 | |
543 | info->fbops = &efifb_ops; |
544 | info->var = efifb_defined; |
545 | info->fix = efifb_fix; |
546 | |
547 | orientation = drm_get_panel_orientation_quirk(width: efifb_defined.xres, |
548 | height: efifb_defined.yres); |
549 | switch (orientation) { |
550 | default: |
551 | info->fbcon_rotate_hint = FB_ROTATE_UR; |
552 | break; |
553 | case DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP: |
554 | info->fbcon_rotate_hint = FB_ROTATE_UD; |
555 | break; |
556 | case DRM_MODE_PANEL_ORIENTATION_LEFT_UP: |
557 | info->fbcon_rotate_hint = FB_ROTATE_CCW; |
558 | break; |
559 | case DRM_MODE_PANEL_ORIENTATION_RIGHT_UP: |
560 | info->fbcon_rotate_hint = FB_ROTATE_CW; |
561 | break; |
562 | } |
563 | |
564 | err = sysfs_create_groups(kobj: &dev->dev.kobj, groups: efifb_groups); |
565 | if (err) { |
566 | pr_err("efifb: cannot add sysfs attrs\n" ); |
567 | goto err_unmap; |
568 | } |
569 | err = fb_alloc_cmap(cmap: &info->cmap, len: 256, transp: 0); |
570 | if (err < 0) { |
571 | pr_err("efifb: cannot allocate colormap\n" ); |
572 | goto err_groups; |
573 | } |
574 | |
575 | err = devm_aperture_acquire_for_platform_device(pdev: dev, base: par->base, size: par->size); |
576 | if (err) { |
577 | pr_err("efifb: cannot acquire aperture\n" ); |
578 | goto err_fb_dealloc_cmap; |
579 | } |
580 | err = register_framebuffer(fb_info: info); |
581 | if (err < 0) { |
582 | pr_err("efifb: cannot register framebuffer\n" ); |
583 | goto err_fb_dealloc_cmap; |
584 | } |
585 | fb_info(info, "%s frame buffer device\n" , info->fix.id); |
586 | return 0; |
587 | |
588 | err_fb_dealloc_cmap: |
589 | fb_dealloc_cmap(cmap: &info->cmap); |
590 | err_groups: |
591 | sysfs_remove_groups(kobj: &dev->dev.kobj, groups: efifb_groups); |
592 | err_unmap: |
593 | if (mem_flags & (EFI_MEMORY_UC | EFI_MEMORY_WC)) |
594 | iounmap(addr: info->screen_base); |
595 | else |
596 | memunmap(addr: info->screen_base); |
597 | err_release_fb: |
598 | framebuffer_release(info); |
599 | err_release_mem: |
600 | if (request_mem_succeeded) |
601 | release_mem_region(efifb_fix.smem_start, size_total); |
602 | return err; |
603 | } |
604 | |
605 | static void efifb_remove(struct platform_device *pdev) |
606 | { |
607 | struct fb_info *info = platform_get_drvdata(pdev); |
608 | |
609 | /* efifb_destroy takes care of info cleanup */ |
610 | unregister_framebuffer(fb_info: info); |
611 | sysfs_remove_groups(kobj: &pdev->dev.kobj, groups: efifb_groups); |
612 | } |
613 | |
614 | static struct platform_driver efifb_driver = { |
615 | .driver = { |
616 | .name = "efi-framebuffer" , |
617 | }, |
618 | .probe = efifb_probe, |
619 | .remove_new = efifb_remove, |
620 | }; |
621 | |
622 | builtin_platform_driver(efifb_driver); |
623 | |