1 | /* |
2 | * linux/drivers/video/fbmem.c |
3 | * |
4 | * Copyright (C) 1994 Martin Schaller |
5 | * |
6 | * 2001 - Documented with DocBook |
7 | * - Brad Douglas <brad@neruo.com> |
8 | * |
9 | * This file is subject to the terms and conditions of the GNU General Public |
10 | * License. See the file COPYING in the main directory of this archive |
11 | * for more details. |
12 | */ |
13 | |
14 | #include <linux/console.h> |
15 | #include <linux/export.h> |
16 | #include <linux/fb.h> |
17 | #include <linux/fbcon.h> |
18 | |
19 | #include <video/nomodeset.h> |
20 | |
21 | #include "fb_internal.h" |
22 | |
23 | /* |
24 | * Frame buffer device initialization and setup routines |
25 | */ |
26 | |
27 | #define FBPIXMAPSIZE (1024 * 8) |
28 | |
29 | struct class *fb_class; |
30 | |
31 | DEFINE_MUTEX(registration_lock); |
32 | struct fb_info *registered_fb[FB_MAX] __read_mostly; |
33 | int num_registered_fb __read_mostly; |
34 | #define for_each_registered_fb(i) \ |
35 | for (i = 0; i < FB_MAX; i++) \ |
36 | if (!registered_fb[i]) {} else |
37 | |
38 | struct fb_info *get_fb_info(unsigned int idx) |
39 | { |
40 | struct fb_info *fb_info; |
41 | |
42 | if (idx >= FB_MAX) |
43 | return ERR_PTR(error: -ENODEV); |
44 | |
45 | mutex_lock(®istration_lock); |
46 | fb_info = registered_fb[idx]; |
47 | if (fb_info) |
48 | refcount_inc(r: &fb_info->count); |
49 | mutex_unlock(lock: ®istration_lock); |
50 | |
51 | return fb_info; |
52 | } |
53 | |
54 | void put_fb_info(struct fb_info *fb_info) |
55 | { |
56 | if (!refcount_dec_and_test(r: &fb_info->count)) |
57 | return; |
58 | if (fb_info->fbops->fb_destroy) |
59 | fb_info->fbops->fb_destroy(fb_info); |
60 | } |
61 | |
62 | /* |
63 | * Helpers |
64 | */ |
65 | |
66 | int fb_get_color_depth(struct fb_var_screeninfo *var, |
67 | struct fb_fix_screeninfo *fix) |
68 | { |
69 | int depth = 0; |
70 | |
71 | if (fix->visual == FB_VISUAL_MONO01 || |
72 | fix->visual == FB_VISUAL_MONO10) |
73 | depth = 1; |
74 | else { |
75 | if (var->green.length == var->blue.length && |
76 | var->green.length == var->red.length && |
77 | var->green.offset == var->blue.offset && |
78 | var->green.offset == var->red.offset) |
79 | depth = var->green.length; |
80 | else |
81 | depth = var->green.length + var->red.length + |
82 | var->blue.length; |
83 | } |
84 | |
85 | return depth; |
86 | } |
87 | EXPORT_SYMBOL(fb_get_color_depth); |
88 | |
89 | /* |
90 | * Data padding functions. |
91 | */ |
92 | void fb_pad_aligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 s_pitch, u32 height) |
93 | { |
94 | __fb_pad_aligned_buffer(dst, d_pitch, src, s_pitch, height); |
95 | } |
96 | EXPORT_SYMBOL(fb_pad_aligned_buffer); |
97 | |
98 | void fb_pad_unaligned_buffer(u8 *dst, u32 d_pitch, u8 *src, u32 idx, u32 height, |
99 | u32 shift_high, u32 shift_low, u32 mod) |
100 | { |
101 | u8 mask = (u8) (0xfff << shift_high), tmp; |
102 | int i, j; |
103 | |
104 | for (i = height; i--; ) { |
105 | for (j = 0; j < idx; j++) { |
106 | tmp = dst[j]; |
107 | tmp &= mask; |
108 | tmp |= *src >> shift_low; |
109 | dst[j] = tmp; |
110 | tmp = *src << shift_high; |
111 | dst[j+1] = tmp; |
112 | src++; |
113 | } |
114 | tmp = dst[idx]; |
115 | tmp &= mask; |
116 | tmp |= *src >> shift_low; |
117 | dst[idx] = tmp; |
118 | if (shift_high < mod) { |
119 | tmp = *src << shift_high; |
120 | dst[idx+1] = tmp; |
121 | } |
122 | src++; |
123 | dst += d_pitch; |
124 | } |
125 | } |
126 | EXPORT_SYMBOL(fb_pad_unaligned_buffer); |
127 | |
128 | /* |
129 | * we need to lock this section since fb_cursor |
130 | * may use fb_imageblit() |
131 | */ |
132 | char* fb_get_buffer_offset(struct fb_info *info, struct fb_pixmap *buf, u32 size) |
133 | { |
134 | u32 align = buf->buf_align - 1, offset; |
135 | char *addr = buf->addr; |
136 | |
137 | /* If IO mapped, we need to sync before access, no sharing of |
138 | * the pixmap is done |
139 | */ |
140 | if (buf->flags & FB_PIXMAP_IO) { |
141 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) |
142 | info->fbops->fb_sync(info); |
143 | return addr; |
144 | } |
145 | |
146 | /* See if we fit in the remaining pixmap space */ |
147 | offset = buf->offset + align; |
148 | offset &= ~align; |
149 | if (offset + size > buf->size) { |
150 | /* We do not fit. In order to be able to re-use the buffer, |
151 | * we must ensure no asynchronous DMA'ing or whatever operation |
152 | * is in progress, we sync for that. |
153 | */ |
154 | if (info->fbops->fb_sync && (buf->flags & FB_PIXMAP_SYNC)) |
155 | info->fbops->fb_sync(info); |
156 | offset = 0; |
157 | } |
158 | buf->offset = offset + size; |
159 | addr += offset; |
160 | |
161 | return addr; |
162 | } |
163 | EXPORT_SYMBOL(fb_get_buffer_offset); |
164 | |
165 | int |
166 | fb_pan_display(struct fb_info *info, struct fb_var_screeninfo *var) |
167 | { |
168 | struct fb_fix_screeninfo *fix = &info->fix; |
169 | unsigned int yres = info->var.yres; |
170 | int err = 0; |
171 | |
172 | if (var->yoffset > 0) { |
173 | if (var->vmode & FB_VMODE_YWRAP) { |
174 | if (!fix->ywrapstep || (var->yoffset % fix->ywrapstep)) |
175 | err = -EINVAL; |
176 | else |
177 | yres = 0; |
178 | } else if (!fix->ypanstep || (var->yoffset % fix->ypanstep)) |
179 | err = -EINVAL; |
180 | } |
181 | |
182 | if (var->xoffset > 0 && (!fix->xpanstep || |
183 | (var->xoffset % fix->xpanstep))) |
184 | err = -EINVAL; |
185 | |
186 | if (err || !info->fbops->fb_pan_display || |
187 | var->yoffset > info->var.yres_virtual - yres || |
188 | var->xoffset > info->var.xres_virtual - info->var.xres) |
189 | return -EINVAL; |
190 | |
191 | if ((err = info->fbops->fb_pan_display(var, info))) |
192 | return err; |
193 | info->var.xoffset = var->xoffset; |
194 | info->var.yoffset = var->yoffset; |
195 | if (var->vmode & FB_VMODE_YWRAP) |
196 | info->var.vmode |= FB_VMODE_YWRAP; |
197 | else |
198 | info->var.vmode &= ~FB_VMODE_YWRAP; |
199 | return 0; |
200 | } |
201 | EXPORT_SYMBOL(fb_pan_display); |
202 | |
203 | static int fb_check_caps(struct fb_info *info, struct fb_var_screeninfo *var, |
204 | u32 activate) |
205 | { |
206 | struct fb_blit_caps caps, fbcaps; |
207 | int err = 0; |
208 | |
209 | memset(&caps, 0, sizeof(caps)); |
210 | memset(&fbcaps, 0, sizeof(fbcaps)); |
211 | caps.flags = (activate & FB_ACTIVATE_ALL) ? 1 : 0; |
212 | fbcon_get_requirement(info, caps: &caps); |
213 | info->fbops->fb_get_caps(info, &fbcaps, var); |
214 | |
215 | if (!bitmap_subset(src1: caps.x, src2: fbcaps.x, FB_MAX_BLIT_WIDTH) || |
216 | !bitmap_subset(src1: caps.y, src2: fbcaps.y, FB_MAX_BLIT_HEIGHT) || |
217 | (fbcaps.len < caps.len)) |
218 | err = -EINVAL; |
219 | |
220 | return err; |
221 | } |
222 | |
223 | int |
224 | fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var) |
225 | { |
226 | int ret = 0; |
227 | u32 activate; |
228 | struct fb_var_screeninfo old_var; |
229 | struct fb_videomode mode; |
230 | struct fb_event event; |
231 | u32 unused; |
232 | |
233 | if (var->activate & FB_ACTIVATE_INV_MODE) { |
234 | struct fb_videomode mode1, mode2; |
235 | |
236 | fb_var_to_videomode(mode: &mode1, var); |
237 | fb_var_to_videomode(mode: &mode2, var: &info->var); |
238 | /* make sure we don't delete the videomode of current var */ |
239 | ret = fb_mode_is_equal(mode1: &mode1, mode2: &mode2); |
240 | if (!ret) { |
241 | ret = fbcon_mode_deleted(info, mode: &mode1); |
242 | if (!ret) |
243 | fb_delete_videomode(mode: &mode1, head: &info->modelist); |
244 | } |
245 | |
246 | return ret ? -EINVAL : 0; |
247 | } |
248 | |
249 | if (!(var->activate & FB_ACTIVATE_FORCE) && |
250 | !memcmp(p: &info->var, q: var, size: sizeof(struct fb_var_screeninfo))) |
251 | return 0; |
252 | |
253 | activate = var->activate; |
254 | |
255 | /* When using FOURCC mode, make sure the red, green, blue and |
256 | * transp fields are set to 0. |
257 | */ |
258 | if ((info->fix.capabilities & FB_CAP_FOURCC) && |
259 | var->grayscale > 1) { |
260 | if (var->red.offset || var->green.offset || |
261 | var->blue.offset || var->transp.offset || |
262 | var->red.length || var->green.length || |
263 | var->blue.length || var->transp.length || |
264 | var->red.msb_right || var->green.msb_right || |
265 | var->blue.msb_right || var->transp.msb_right) |
266 | return -EINVAL; |
267 | } |
268 | |
269 | if (!info->fbops->fb_check_var) { |
270 | *var = info->var; |
271 | return 0; |
272 | } |
273 | |
274 | /* bitfill_aligned() assumes that it's at least 8x8 */ |
275 | if (var->xres < 8 || var->yres < 8) |
276 | return -EINVAL; |
277 | |
278 | /* Too huge resolution causes multiplication overflow. */ |
279 | if (check_mul_overflow(var->xres, var->yres, &unused) || |
280 | check_mul_overflow(var->xres_virtual, var->yres_virtual, &unused)) |
281 | return -EINVAL; |
282 | |
283 | ret = info->fbops->fb_check_var(var, info); |
284 | |
285 | if (ret) |
286 | return ret; |
287 | |
288 | /* verify that virtual resolution >= physical resolution */ |
289 | if (var->xres_virtual < var->xres || |
290 | var->yres_virtual < var->yres) { |
291 | pr_warn("WARNING: fbcon: Driver '%s' missed to adjust virtual screen size (%ux%u vs. %ux%u)\n" , |
292 | info->fix.id, |
293 | var->xres_virtual, var->yres_virtual, |
294 | var->xres, var->yres); |
295 | return -EINVAL; |
296 | } |
297 | |
298 | if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW) |
299 | return 0; |
300 | |
301 | if (info->fbops->fb_get_caps) { |
302 | ret = fb_check_caps(info, var, activate); |
303 | |
304 | if (ret) |
305 | return ret; |
306 | } |
307 | |
308 | old_var = info->var; |
309 | info->var = *var; |
310 | |
311 | if (info->fbops->fb_set_par) { |
312 | ret = info->fbops->fb_set_par(info); |
313 | |
314 | if (ret) { |
315 | info->var = old_var; |
316 | printk(KERN_WARNING "detected " |
317 | "fb_set_par error, " |
318 | "error code: %d\n" , ret); |
319 | return ret; |
320 | } |
321 | } |
322 | |
323 | fb_pan_display(info, &info->var); |
324 | fb_set_cmap(cmap: &info->cmap, fb_info: info); |
325 | fb_var_to_videomode(mode: &mode, var: &info->var); |
326 | |
327 | if (info->modelist.prev && info->modelist.next && |
328 | !list_empty(head: &info->modelist)) |
329 | ret = fb_add_videomode(mode: &mode, head: &info->modelist); |
330 | |
331 | if (ret) |
332 | return ret; |
333 | |
334 | event.info = info; |
335 | event.data = &mode; |
336 | fb_notifier_call_chain(FB_EVENT_MODE_CHANGE, v: &event); |
337 | |
338 | return 0; |
339 | } |
340 | EXPORT_SYMBOL(fb_set_var); |
341 | |
342 | int |
343 | fb_blank(struct fb_info *info, int blank) |
344 | { |
345 | struct fb_event event; |
346 | int ret = -EINVAL; |
347 | |
348 | if (blank > FB_BLANK_POWERDOWN) |
349 | blank = FB_BLANK_POWERDOWN; |
350 | |
351 | event.info = info; |
352 | event.data = ␣ |
353 | |
354 | if (info->fbops->fb_blank) |
355 | ret = info->fbops->fb_blank(blank, info); |
356 | |
357 | if (!ret) |
358 | fb_notifier_call_chain(FB_EVENT_BLANK, v: &event); |
359 | |
360 | return ret; |
361 | } |
362 | EXPORT_SYMBOL(fb_blank); |
363 | |
364 | static int fb_check_foreignness(struct fb_info *fi) |
365 | { |
366 | const bool foreign_endian = fi->flags & FBINFO_FOREIGN_ENDIAN; |
367 | |
368 | fi->flags &= ~FBINFO_FOREIGN_ENDIAN; |
369 | |
370 | #ifdef __BIG_ENDIAN |
371 | fi->flags |= foreign_endian ? 0 : FBINFO_BE_MATH; |
372 | #else |
373 | fi->flags |= foreign_endian ? FBINFO_BE_MATH : 0; |
374 | #endif /* __BIG_ENDIAN */ |
375 | |
376 | if (fi->flags & FBINFO_BE_MATH && !fb_be_math(info: fi)) { |
377 | pr_err("%s: enable CONFIG_FB_BIG_ENDIAN to " |
378 | "support this framebuffer\n" , fi->fix.id); |
379 | return -ENOSYS; |
380 | } else if (!(fi->flags & FBINFO_BE_MATH) && fb_be_math(info: fi)) { |
381 | pr_err("%s: enable CONFIG_FB_LITTLE_ENDIAN to " |
382 | "support this framebuffer\n" , fi->fix.id); |
383 | return -ENOSYS; |
384 | } |
385 | |
386 | return 0; |
387 | } |
388 | |
389 | static int do_register_framebuffer(struct fb_info *fb_info) |
390 | { |
391 | int i; |
392 | struct fb_videomode mode; |
393 | |
394 | if (fb_check_foreignness(fi: fb_info)) |
395 | return -ENOSYS; |
396 | |
397 | if (num_registered_fb == FB_MAX) |
398 | return -ENXIO; |
399 | |
400 | num_registered_fb++; |
401 | for (i = 0 ; i < FB_MAX; i++) |
402 | if (!registered_fb[i]) |
403 | break; |
404 | fb_info->node = i; |
405 | refcount_set(r: &fb_info->count, n: 1); |
406 | mutex_init(&fb_info->lock); |
407 | mutex_init(&fb_info->mm_lock); |
408 | |
409 | fb_device_create(fb_info); |
410 | |
411 | if (fb_info->pixmap.addr == NULL) { |
412 | fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); |
413 | if (fb_info->pixmap.addr) { |
414 | fb_info->pixmap.size = FBPIXMAPSIZE; |
415 | fb_info->pixmap.buf_align = 1; |
416 | fb_info->pixmap.scan_align = 1; |
417 | fb_info->pixmap.access_align = 32; |
418 | fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; |
419 | } |
420 | } |
421 | fb_info->pixmap.offset = 0; |
422 | |
423 | if (bitmap_empty(src: fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH)) |
424 | bitmap_fill(dst: fb_info->pixmap.blit_x, FB_MAX_BLIT_WIDTH); |
425 | |
426 | if (bitmap_empty(src: fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT)) |
427 | bitmap_fill(dst: fb_info->pixmap.blit_y, FB_MAX_BLIT_HEIGHT); |
428 | |
429 | if (!fb_info->modelist.prev || !fb_info->modelist.next) |
430 | INIT_LIST_HEAD(list: &fb_info->modelist); |
431 | |
432 | if (fb_info->skip_vt_switch) |
433 | pm_vt_switch_required(dev: fb_info->device, required: false); |
434 | else |
435 | pm_vt_switch_required(dev: fb_info->device, required: true); |
436 | |
437 | fb_var_to_videomode(mode: &mode, var: &fb_info->var); |
438 | fb_add_videomode(mode: &mode, head: &fb_info->modelist); |
439 | registered_fb[i] = fb_info; |
440 | |
441 | #ifdef CONFIG_GUMSTIX_AM200EPD |
442 | { |
443 | struct fb_event event; |
444 | event.info = fb_info; |
445 | fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); |
446 | } |
447 | #endif |
448 | |
449 | return fbcon_fb_registered(info: fb_info); |
450 | } |
451 | |
452 | static void unbind_console(struct fb_info *fb_info) |
453 | { |
454 | int i = fb_info->node; |
455 | |
456 | if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)) |
457 | return; |
458 | |
459 | fbcon_fb_unbind(info: fb_info); |
460 | } |
461 | |
462 | static void unlink_framebuffer(struct fb_info *fb_info) |
463 | { |
464 | int i; |
465 | |
466 | i = fb_info->node; |
467 | if (WARN_ON(i < 0 || i >= FB_MAX || registered_fb[i] != fb_info)) |
468 | return; |
469 | |
470 | fb_device_destroy(fb_info); |
471 | pm_vt_switch_unregister(dev: fb_info->device); |
472 | unbind_console(fb_info); |
473 | } |
474 | |
475 | static void do_unregister_framebuffer(struct fb_info *fb_info) |
476 | { |
477 | unlink_framebuffer(fb_info); |
478 | if (fb_info->pixmap.addr && |
479 | (fb_info->pixmap.flags & FB_PIXMAP_DEFAULT)) { |
480 | kfree(objp: fb_info->pixmap.addr); |
481 | fb_info->pixmap.addr = NULL; |
482 | } |
483 | |
484 | fb_destroy_modelist(head: &fb_info->modelist); |
485 | registered_fb[fb_info->node] = NULL; |
486 | num_registered_fb--; |
487 | #ifdef CONFIG_GUMSTIX_AM200EPD |
488 | { |
489 | struct fb_event event; |
490 | event.info = fb_info; |
491 | fb_notifier_call_chain(FB_EVENT_FB_UNREGISTERED, &event); |
492 | } |
493 | #endif |
494 | fbcon_fb_unregistered(info: fb_info); |
495 | |
496 | /* this may free fb info */ |
497 | put_fb_info(fb_info); |
498 | } |
499 | |
500 | /** |
501 | * register_framebuffer - registers a frame buffer device |
502 | * @fb_info: frame buffer info structure |
503 | * |
504 | * Registers a frame buffer device @fb_info. |
505 | * |
506 | * Returns negative errno on error, or zero for success. |
507 | * |
508 | */ |
509 | int |
510 | register_framebuffer(struct fb_info *fb_info) |
511 | { |
512 | int ret; |
513 | |
514 | mutex_lock(®istration_lock); |
515 | ret = do_register_framebuffer(fb_info); |
516 | mutex_unlock(lock: ®istration_lock); |
517 | |
518 | return ret; |
519 | } |
520 | EXPORT_SYMBOL(register_framebuffer); |
521 | |
522 | /** |
523 | * unregister_framebuffer - releases a frame buffer device |
524 | * @fb_info: frame buffer info structure |
525 | * |
526 | * Unregisters a frame buffer device @fb_info. |
527 | * |
528 | * Returns negative errno on error, or zero for success. |
529 | * |
530 | * This function will also notify the framebuffer console |
531 | * to release the driver. |
532 | * |
533 | * This is meant to be called within a driver's module_exit() |
534 | * function. If this is called outside module_exit(), ensure |
535 | * that the driver implements fb_open() and fb_release() to |
536 | * check that no processes are using the device. |
537 | */ |
538 | void |
539 | unregister_framebuffer(struct fb_info *fb_info) |
540 | { |
541 | mutex_lock(®istration_lock); |
542 | do_unregister_framebuffer(fb_info); |
543 | mutex_unlock(lock: ®istration_lock); |
544 | } |
545 | EXPORT_SYMBOL(unregister_framebuffer); |
546 | |
547 | /** |
548 | * fb_set_suspend - low level driver signals suspend |
549 | * @info: framebuffer affected |
550 | * @state: 0 = resuming, !=0 = suspending |
551 | * |
552 | * This is meant to be used by low level drivers to |
553 | * signal suspend/resume to the core & clients. |
554 | * It must be called with the console semaphore held |
555 | */ |
556 | void fb_set_suspend(struct fb_info *info, int state) |
557 | { |
558 | WARN_CONSOLE_UNLOCKED(); |
559 | |
560 | if (state) { |
561 | fbcon_suspended(info); |
562 | info->state = FBINFO_STATE_SUSPENDED; |
563 | } else { |
564 | info->state = FBINFO_STATE_RUNNING; |
565 | fbcon_resumed(info); |
566 | } |
567 | } |
568 | EXPORT_SYMBOL(fb_set_suspend); |
569 | |
570 | static int __init fbmem_init(void) |
571 | { |
572 | int ret; |
573 | |
574 | fb_class = class_create(name: "graphics" ); |
575 | if (IS_ERR(ptr: fb_class)) { |
576 | ret = PTR_ERR(ptr: fb_class); |
577 | pr_err("Unable to create fb class; errno = %d\n" , ret); |
578 | goto err_fb_class; |
579 | } |
580 | |
581 | ret = fb_init_procfs(); |
582 | if (ret) |
583 | goto err_class_destroy; |
584 | |
585 | ret = fb_register_chrdev(); |
586 | if (ret) |
587 | goto err_fb_cleanup_procfs; |
588 | |
589 | fb_console_init(); |
590 | |
591 | return 0; |
592 | |
593 | err_fb_cleanup_procfs: |
594 | fb_cleanup_procfs(); |
595 | err_class_destroy: |
596 | class_destroy(cls: fb_class); |
597 | err_fb_class: |
598 | fb_class = NULL; |
599 | return ret; |
600 | } |
601 | |
602 | #ifdef MODULE |
603 | static void __exit fbmem_exit(void) |
604 | { |
605 | fb_console_exit(); |
606 | fb_unregister_chrdev(); |
607 | fb_cleanup_procfs(); |
608 | class_destroy(fb_class); |
609 | } |
610 | |
611 | module_init(fbmem_init); |
612 | module_exit(fbmem_exit); |
613 | MODULE_LICENSE("GPL" ); |
614 | MODULE_DESCRIPTION("Framebuffer base" ); |
615 | #else |
616 | subsys_initcall(fbmem_init); |
617 | #endif |
618 | |
619 | int fb_new_modelist(struct fb_info *info) |
620 | { |
621 | struct fb_var_screeninfo var = info->var; |
622 | struct list_head *pos, *n; |
623 | struct fb_modelist *modelist; |
624 | struct fb_videomode *m, mode; |
625 | int err; |
626 | |
627 | list_for_each_safe(pos, n, &info->modelist) { |
628 | modelist = list_entry(pos, struct fb_modelist, list); |
629 | m = &modelist->mode; |
630 | fb_videomode_to_var(var: &var, mode: m); |
631 | var.activate = FB_ACTIVATE_TEST; |
632 | err = fb_set_var(info, &var); |
633 | fb_var_to_videomode(mode: &mode, var: &var); |
634 | if (err || !fb_mode_is_equal(mode1: m, mode2: &mode)) { |
635 | list_del(entry: pos); |
636 | kfree(objp: pos); |
637 | } |
638 | } |
639 | |
640 | if (list_empty(head: &info->modelist)) |
641 | return 1; |
642 | |
643 | fbcon_new_modelist(info); |
644 | |
645 | return 0; |
646 | } |
647 | |
648 | bool fb_modesetting_disabled(const char *drvname) |
649 | { |
650 | bool fwonly = video_firmware_drivers_only(); |
651 | |
652 | if (fwonly) |
653 | pr_warn("Driver %s not loading because of nomodeset parameter\n" , |
654 | drvname); |
655 | |
656 | return fwonly; |
657 | } |
658 | EXPORT_SYMBOL(fb_modesetting_disabled); |
659 | |
660 | MODULE_LICENSE("GPL" ); |
661 | |