2 * Copyright (c) 2006-2009 Red Hat Inc.
3 * Copyright (c) 2006-2008 Intel Corporation
4 * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
6 * DRM framebuffer helper functions
8 * Permission to use, copy, modify, distribute, and sell this software and its
9 * documentation for any purpose is hereby granted without fee, provided that
10 * the above copyright notice appear in all copies and that both that copyright
11 * notice and this permission notice appear in supporting documentation, and
12 * that the name of the copyright holders not be used in advertising or
13 * publicity pertaining to distribution of the software without specific,
14 * written prior permission. The copyright holders make no representations
15 * about the suitability of this software for any purpose. It is provided "as
16 * is" without express or implied warranty.
18 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
19 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
20 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
21 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
22 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
23 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
27 * Dave Airlie <airlied@linux.ie>
28 * Jesse Barnes <jesse.barnes@intel.com>
30 #include <linux/sysrq.h>
34 #include "drm_fb_helper.h"
35 #include "drm_crtc_helper.h"
37 MODULE_AUTHOR("David Airlie, Jesse Barnes");
38 MODULE_DESCRIPTION("DRM KMS helper");
39 MODULE_LICENSE("GPL and additional rights");
41 static LIST_HEAD(kernel_fb_helper_list);
43 bool drm_fb_helper_force_kernel_mode(void)
46 bool ret, error = false;
47 struct drm_fb_helper *helper;
49 if (list_empty(&kernel_fb_helper_list))
52 list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
53 for (i = 0; i < helper->crtc_count; i++) {
54 struct drm_mode_set *mode_set = &helper->crtc_info[i].mode_set;
55 ret = drm_crtc_helper_set_config(mode_set);
63 int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed,
66 DRM_ERROR("panic occurred, switching back to text console\n");
67 return drm_fb_helper_force_kernel_mode();
70 EXPORT_SYMBOL(drm_fb_helper_panic);
72 static struct notifier_block paniced = {
73 .notifier_call = drm_fb_helper_panic,
77 * drm_fb_helper_restore - restore the framebuffer console (kernel) config
79 * Restore's the kernel's fbcon mode, used for lastclose & panic paths.
81 void drm_fb_helper_restore(void)
84 ret = drm_fb_helper_force_kernel_mode();
86 DRM_ERROR("Failed to restore crtc configuration\n");
88 EXPORT_SYMBOL(drm_fb_helper_restore);
90 static void drm_fb_helper_restore_work_fn(struct work_struct *ignored)
92 drm_fb_helper_restore();
94 static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn);
96 static void drm_fb_helper_sysrq(int dummy1, struct tty_struct *dummy3)
98 schedule_work(&drm_fb_helper_restore_work);
101 static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = {
102 .handler = drm_fb_helper_sysrq,
103 .help_msg = "force-fb(V)",
104 .action_msg = "Restore framebuffer console",
107 static void drm_fb_helper_on(struct fb_info *info)
109 struct drm_fb_helper *fb_helper = info->par;
110 struct drm_device *dev = fb_helper->dev;
111 struct drm_crtc *crtc;
112 struct drm_encoder *encoder;
116 * For each CRTC in this fb, turn the crtc on then,
117 * find all associated encoders and turn them on.
119 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
120 struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
122 for (i = 0; i < fb_helper->crtc_count; i++) {
123 if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
127 mutex_lock(&dev->mode_config.mutex);
128 crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
129 mutex_unlock(&dev->mode_config.mutex);
131 /* Found a CRTC on this fb, now find encoders */
132 list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
133 if (encoder->crtc == crtc) {
134 struct drm_encoder_helper_funcs *encoder_funcs;
136 encoder_funcs = encoder->helper_private;
137 mutex_lock(&dev->mode_config.mutex);
138 encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON);
139 mutex_unlock(&dev->mode_config.mutex);
145 static void drm_fb_helper_off(struct fb_info *info, int dpms_mode)
147 struct drm_fb_helper *fb_helper = info->par;
148 struct drm_device *dev = fb_helper->dev;
149 struct drm_crtc *crtc;
150 struct drm_encoder *encoder;
154 * For each CRTC in this fb, find all associated encoders
155 * and turn them off, then turn off the CRTC.
157 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
158 struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
160 for (i = 0; i < fb_helper->crtc_count; i++) {
161 if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
165 /* Found a CRTC on this fb, now find encoders */
166 list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
167 if (encoder->crtc == crtc) {
168 struct drm_encoder_helper_funcs *encoder_funcs;
170 encoder_funcs = encoder->helper_private;
171 mutex_lock(&dev->mode_config.mutex);
172 encoder_funcs->dpms(encoder, dpms_mode);
173 mutex_unlock(&dev->mode_config.mutex);
176 if (dpms_mode == DRM_MODE_DPMS_OFF) {
177 mutex_lock(&dev->mode_config.mutex);
178 crtc_funcs->dpms(crtc, dpms_mode);
179 mutex_unlock(&dev->mode_config.mutex);
184 int drm_fb_helper_blank(int blank, struct fb_info *info)
187 case FB_BLANK_UNBLANK:
188 drm_fb_helper_on(info);
190 case FB_BLANK_NORMAL:
191 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
193 case FB_BLANK_HSYNC_SUSPEND:
194 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
196 case FB_BLANK_VSYNC_SUSPEND:
197 drm_fb_helper_off(info, DRM_MODE_DPMS_SUSPEND);
199 case FB_BLANK_POWERDOWN:
200 drm_fb_helper_off(info, DRM_MODE_DPMS_OFF);
205 EXPORT_SYMBOL(drm_fb_helper_blank);
207 static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
211 for (i = 0; i < helper->crtc_count; i++)
212 kfree(helper->crtc_info[i].mode_set.connectors);
213 kfree(helper->crtc_info);
216 int drm_fb_helper_init_crtc_count(struct drm_fb_helper *helper, int crtc_count, int max_conn_count)
218 struct drm_device *dev = helper->dev;
219 struct drm_crtc *crtc;
223 helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL);
224 if (!helper->crtc_info)
227 helper->crtc_count = crtc_count;
229 for (i = 0; i < crtc_count; i++) {
230 helper->crtc_info[i].mode_set.connectors =
231 kcalloc(max_conn_count,
232 sizeof(struct drm_connector *),
235 if (!helper->crtc_info[i].mode_set.connectors) {
239 helper->crtc_info[i].mode_set.num_connectors = 0;
243 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
244 helper->crtc_info[i].crtc_id = crtc->base.id;
245 helper->crtc_info[i].mode_set.crtc = crtc;
248 helper->conn_limit = max_conn_count;
251 drm_fb_helper_crtc_free(helper);
254 EXPORT_SYMBOL(drm_fb_helper_init_crtc_count);
256 int drm_fb_helper_setcolreg(unsigned regno,
261 struct fb_info *info)
263 struct drm_fb_helper *fb_helper = info->par;
264 struct drm_device *dev = fb_helper->dev;
265 struct drm_crtc *crtc;
268 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
269 struct drm_framebuffer *fb = fb_helper->fb;
271 for (i = 0; i < fb_helper->crtc_count; i++) {
272 if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
275 if (i == fb_helper->crtc_count)
281 if (fb->depth == 8) {
282 fb_helper->funcs->gamma_set(crtc, red, green, blue, regno);
289 fb->pseudo_palette[regno] = ((red & 0xf800) >> 1) |
290 ((green & 0xf800) >> 6) |
291 ((blue & 0xf800) >> 11);
294 fb->pseudo_palette[regno] = (red & 0xf800) |
295 ((green & 0xfc00) >> 5) |
296 ((blue & 0xf800) >> 11);
300 fb->pseudo_palette[regno] =
301 (((red >> 8) & 0xff) << info->var.red.offset) |
302 (((green >> 8) & 0xff) << info->var.green.offset) |
303 (((blue >> 8) & 0xff) << info->var.blue.offset);
310 EXPORT_SYMBOL(drm_fb_helper_setcolreg);
312 int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
313 struct fb_info *info)
315 struct drm_fb_helper *fb_helper = info->par;
316 struct drm_framebuffer *fb = fb_helper->fb;
319 if (var->pixclock == -1 || !var->pixclock)
322 /* Need to resize the fb object !!! */
323 if (var->xres > fb->width || var->yres > fb->height) {
324 DRM_ERROR("Requested width/height is greater than current fb "
325 "object %dx%d > %dx%d\n", var->xres, var->yres,
326 fb->width, fb->height);
327 DRM_ERROR("Need resizing code.\n");
331 switch (var->bits_per_pixel) {
333 depth = (var->green.length == 6) ? 16 : 15;
336 depth = (var->transp.length > 0) ? 32 : 24;
339 depth = var->bits_per_pixel;
346 var->green.offset = 0;
347 var->blue.offset = 0;
349 var->green.length = 8;
350 var->blue.length = 8;
351 var->transp.length = 0;
352 var->transp.offset = 0;
355 var->red.offset = 10;
356 var->green.offset = 5;
357 var->blue.offset = 0;
359 var->green.length = 5;
360 var->blue.length = 5;
361 var->transp.length = 1;
362 var->transp.offset = 15;
365 var->red.offset = 11;
366 var->green.offset = 5;
367 var->blue.offset = 0;
369 var->green.length = 6;
370 var->blue.length = 5;
371 var->transp.length = 0;
372 var->transp.offset = 0;
375 var->red.offset = 16;
376 var->green.offset = 8;
377 var->blue.offset = 0;
379 var->green.length = 8;
380 var->blue.length = 8;
381 var->transp.length = 0;
382 var->transp.offset = 0;
385 var->red.offset = 16;
386 var->green.offset = 8;
387 var->blue.offset = 0;
389 var->green.length = 8;
390 var->blue.length = 8;
391 var->transp.length = 8;
392 var->transp.offset = 24;
399 EXPORT_SYMBOL(drm_fb_helper_check_var);
401 /* this will let fbcon do the mode init */
402 int drm_fb_helper_set_par(struct fb_info *info)
404 struct drm_fb_helper *fb_helper = info->par;
405 struct drm_device *dev = fb_helper->dev;
406 struct fb_var_screeninfo *var = &info->var;
407 struct drm_crtc *crtc;
411 if (var->pixclock != -1) {
412 DRM_ERROR("PIXEL CLCOK SET\n");
416 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
418 for (i = 0; i < fb_helper->crtc_count; i++) {
419 if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
422 if (i == fb_helper->crtc_count)
425 if (crtc->fb == fb_helper->crtc_info[i].mode_set.fb) {
426 mutex_lock(&dev->mode_config.mutex);
427 ret = crtc->funcs->set_config(&fb_helper->crtc_info->mode_set);
428 mutex_unlock(&dev->mode_config.mutex);
435 EXPORT_SYMBOL(drm_fb_helper_set_par);
437 int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
438 struct fb_info *info)
440 struct drm_fb_helper *fb_helper = info->par;
441 struct drm_device *dev = fb_helper->dev;
442 struct drm_mode_set *modeset;
443 struct drm_crtc *crtc;
447 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
448 for (i = 0; i < fb_helper->crtc_count; i++) {
449 if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
453 if (i == fb_helper->crtc_count)
456 modeset = &fb_helper->crtc_info[i].mode_set;
458 modeset->x = var->xoffset;
459 modeset->y = var->yoffset;
461 if (modeset->num_connectors) {
462 mutex_lock(&dev->mode_config.mutex);
463 ret = crtc->funcs->set_config(modeset);
464 mutex_unlock(&dev->mode_config.mutex);
466 info->var.xoffset = var->xoffset;
467 info->var.yoffset = var->yoffset;
473 EXPORT_SYMBOL(drm_fb_helper_pan_display);
475 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
476 int (*fb_create)(struct drm_device *dev,
479 uint32_t surface_width,
480 uint32_t surface_height,
481 struct drm_framebuffer **fb_ptr))
483 struct drm_crtc *crtc;
484 struct drm_connector *connector;
485 unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
486 unsigned int surface_width = 0, surface_height = 0;
489 int ret, i, conn_count = 0;
490 struct fb_info *info;
491 struct drm_framebuffer *fb;
492 struct drm_mode_set *modeset = NULL;
493 struct drm_fb_helper *fb_helper;
495 /* first up get a count of crtcs now in use and new min/maxes width/heights */
496 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
497 if (drm_helper_crtc_in_use(crtc)) {
498 if (crtc->desired_mode) {
499 if (crtc->desired_mode->hdisplay < fb_width)
500 fb_width = crtc->desired_mode->hdisplay;
502 if (crtc->desired_mode->vdisplay < fb_height)
503 fb_height = crtc->desired_mode->vdisplay;
505 if (crtc->desired_mode->hdisplay > surface_width)
506 surface_width = crtc->desired_mode->hdisplay;
508 if (crtc->desired_mode->vdisplay > surface_height)
509 surface_height = crtc->desired_mode->vdisplay;
515 if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
516 /* hmm everyone went away - assume VGA cable just fell out
517 and will come back later. */
521 /* do we have an fb already? */
522 if (list_empty(&dev->mode_config.fb_kernel_list)) {
523 ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
524 surface_height, &fb);
529 fb = list_first_entry(&dev->mode_config.fb_kernel_list,
530 struct drm_framebuffer, filp_head);
532 /* if someone hotplugs something bigger than we have already allocated, we are pwned.
533 As really we can't resize an fbdev that is in the wild currently due to fbdev
534 not really being designed for the lower layers moving stuff around under it.
535 - so in the grand style of things - punt. */
536 if ((fb->width < surface_width) ||
537 (fb->height < surface_height)) {
538 DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
544 fb_helper = info->par;
547 /* okay we need to setup new connector sets in the crtcs */
548 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
549 modeset = &fb_helper->crtc_info[crtc_count].mode_set;
552 list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
553 if (connector->encoder)
554 if (connector->encoder->crtc == modeset->crtc) {
555 modeset->connectors[conn_count] = connector;
557 if (conn_count > fb_helper->conn_limit)
562 for (i = conn_count; i < fb_helper->conn_limit; i++)
563 modeset->connectors[i] = NULL;
565 modeset->crtc = crtc;
568 modeset->num_connectors = conn_count;
569 if (modeset->crtc->desired_mode) {
571 drm_mode_destroy(dev, modeset->mode);
572 modeset->mode = drm_mode_duplicate(dev,
573 modeset->crtc->desired_mode);
576 fb_helper->crtc_count = crtc_count;
580 info->var.pixclock = -1;
581 if (register_framebuffer(info) < 0)
584 drm_fb_helper_set_par(info);
586 printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
589 /* Switch back to kernel console on panic */
590 /* multi card linked list maybe */
591 if (list_empty(&kernel_fb_helper_list)) {
592 printk(KERN_INFO "registered panic notifier\n");
593 atomic_notifier_chain_register(&panic_notifier_list,
595 register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
597 list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
600 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
602 void drm_fb_helper_free(struct drm_fb_helper *helper)
604 list_del(&helper->kernel_fb_list);
605 if (list_empty(&kernel_fb_helper_list)) {
606 printk(KERN_INFO "unregistered panic notifier\n");
607 atomic_notifier_chain_unregister(&panic_notifier_list,
609 unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
611 drm_fb_helper_crtc_free(helper);
613 EXPORT_SYMBOL(drm_fb_helper_free);
615 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch)
617 info->fix.type = FB_TYPE_PACKED_PIXELS;
618 info->fix.visual = FB_VISUAL_TRUECOLOR;
619 info->fix.type_aux = 0;
620 info->fix.xpanstep = 1; /* doing it in hw */
621 info->fix.ypanstep = 1; /* doing it in hw */
622 info->fix.ywrapstep = 0;
623 info->fix.accel = FB_ACCEL_NONE;
624 info->fix.type_aux = 0;
626 info->fix.line_length = pitch;
629 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
631 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
632 uint32_t fb_width, uint32_t fb_height)
634 info->pseudo_palette = fb->pseudo_palette;
635 info->var.xres_virtual = fb->width;
636 info->var.yres_virtual = fb->height;
637 info->var.bits_per_pixel = fb->bits_per_pixel;
638 info->var.xoffset = 0;
639 info->var.yoffset = 0;
640 info->var.activate = FB_ACTIVATE_NOW;
641 info->var.height = -1;
642 info->var.width = -1;
646 info->var.red.offset = 0;
647 info->var.green.offset = 0;
648 info->var.blue.offset = 0;
649 info->var.red.length = 8; /* 8bit DAC */
650 info->var.green.length = 8;
651 info->var.blue.length = 8;
652 info->var.transp.offset = 0;
653 info->var.transp.length = 0;
656 info->var.red.offset = 10;
657 info->var.green.offset = 5;
658 info->var.blue.offset = 0;
659 info->var.red.length = 5;
660 info->var.green.length = 5;
661 info->var.blue.length = 5;
662 info->var.transp.offset = 15;
663 info->var.transp.length = 1;
666 info->var.red.offset = 11;
667 info->var.green.offset = 5;
668 info->var.blue.offset = 0;
669 info->var.red.length = 5;
670 info->var.green.length = 6;
671 info->var.blue.length = 5;
672 info->var.transp.offset = 0;
675 info->var.red.offset = 16;
676 info->var.green.offset = 8;
677 info->var.blue.offset = 0;
678 info->var.red.length = 8;
679 info->var.green.length = 8;
680 info->var.blue.length = 8;
681 info->var.transp.offset = 0;
682 info->var.transp.length = 0;
685 info->var.red.offset = 16;
686 info->var.green.offset = 8;
687 info->var.blue.offset = 0;
688 info->var.red.length = 8;
689 info->var.green.length = 8;
690 info->var.blue.length = 8;
691 info->var.transp.offset = 24;
692 info->var.transp.length = 8;
698 info->var.xres = fb_width;
699 info->var.yres = fb_height;
701 EXPORT_SYMBOL(drm_fb_helper_fill_var);