drm/kms: make fb helper work for all drivers.
[safe/jmp/linux-2.6] / drivers / gpu / drm / drm_fb_helper.c
1 /*
2  * Copyright (c) 2006-2009 Red Hat Inc.
3  * Copyright (c) 2006-2008 Intel Corporation
4  * Copyright (c) 2007 Dave Airlie <airlied@linux.ie>
5  *
6  * DRM framebuffer helper functions
7  *
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.
17  *
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
24  * OF THIS SOFTWARE.
25  *
26  * Authors:
27  *      Dave Airlie <airlied@linux.ie>
28  *      Jesse Barnes <jesse.barnes@intel.com>
29  */
30 #include <linux/sysrq.h>
31 #include <linux/fb.h>
32 #include "drmP.h"
33 #include "drm_crtc.h"
34 #include "drm_fb_helper.h"
35 #include "drm_crtc_helper.h"
36
37 MODULE_AUTHOR("David Airlie, Jesse Barnes");
38 MODULE_DESCRIPTION("DRM KMS helper");
39 MODULE_LICENSE("GPL and additional rights");
40
41 static LIST_HEAD(kernel_fb_helper_list);
42
43 int drm_fb_helper_add_connector(struct drm_connector *connector)
44 {
45         connector->fb_helper_private = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL);
46         if (!connector->fb_helper_private)
47                 return -ENOMEM;
48
49         return 0;
50 }
51 EXPORT_SYMBOL(drm_fb_helper_add_connector);
52
53 static int my_atoi(const char *name)
54 {
55         int val = 0;
56
57         for (;; name++) {
58                 switch (*name) {
59                 case '0' ... '9':
60                         val = 10*val+(*name-'0');
61                         break;
62                 default:
63                         return val;
64                 }
65         }
66 }
67
68 /**
69  * drm_fb_helper_connector_parse_command_line - parse command line for connector
70  * @connector - connector to parse line for
71  * @mode_option - per connector mode option
72  *
73  * This parses the connector specific then generic command lines for
74  * modes and options to configure the connector.
75  *
76  * This uses the same parameters as the fb modedb.c, except for extra
77  *      <xres>x<yres>[M][R][-<bpp>][@<refresh>][i][m][eDd]
78  *
79  * enable/enable Digital/disable bit at the end
80  */
81 static bool drm_fb_helper_connector_parse_command_line(struct drm_connector *connector,
82                                                        const char *mode_option)
83 {
84         const char *name;
85         unsigned int namelen;
86         int res_specified = 0, bpp_specified = 0, refresh_specified = 0;
87         unsigned int xres = 0, yres = 0, bpp = 32, refresh = 0;
88         int yres_specified = 0, cvt = 0, rb = 0, interlace = 0, margins = 0;
89         int i;
90         enum drm_connector_force force = DRM_FORCE_UNSPECIFIED;
91         struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
92         struct drm_fb_helper_cmdline_mode *cmdline_mode;
93
94         if (!fb_help_conn)
95                 return false;
96
97         cmdline_mode = &fb_help_conn->cmdline_mode;
98         if (!mode_option)
99                 mode_option = fb_mode_option;
100
101         if (!mode_option) {
102                 cmdline_mode->specified = false;
103                 return false;
104         }
105
106         name = mode_option;
107         namelen = strlen(name);
108         for (i = namelen-1; i >= 0; i--) {
109                 switch (name[i]) {
110                 case '@':
111                         namelen = i;
112                         if (!refresh_specified && !bpp_specified &&
113                             !yres_specified) {
114                                 refresh = my_atoi(&name[i+1]);
115                                 refresh_specified = 1;
116                                 if (cvt || rb)
117                                         cvt = 0;
118                         } else
119                                 goto done;
120                         break;
121                 case '-':
122                         namelen = i;
123                         if (!bpp_specified && !yres_specified) {
124                                 bpp = my_atoi(&name[i+1]);
125                                 bpp_specified = 1;
126                                 if (cvt || rb)
127                                         cvt = 0;
128                         } else
129                                 goto done;
130                         break;
131                 case 'x':
132                         if (!yres_specified) {
133                                 yres = my_atoi(&name[i+1]);
134                                 yres_specified = 1;
135                         } else
136                                 goto done;
137                 case '0' ... '9':
138                         break;
139                 case 'M':
140                         if (!yres_specified)
141                                 cvt = 1;
142                         break;
143                 case 'R':
144                         if (!cvt)
145                                 rb = 1;
146                         break;
147                 case 'm':
148                         if (!cvt)
149                                 margins = 1;
150                         break;
151                 case 'i':
152                         if (!cvt)
153                                 interlace = 1;
154                         break;
155                 case 'e':
156                         force = DRM_FORCE_ON;
157                         break;
158                 case 'D':
159                         if ((connector->connector_type != DRM_MODE_CONNECTOR_DVII) ||
160                             (connector->connector_type != DRM_MODE_CONNECTOR_HDMIB))
161                                 force = DRM_FORCE_ON;
162                         else
163                                 force = DRM_FORCE_ON_DIGITAL;
164                         break;
165                 case 'd':
166                         force = DRM_FORCE_OFF;
167                         break;
168                 default:
169                         goto done;
170                 }
171         }
172         if (i < 0 && yres_specified) {
173                 xres = my_atoi(name);
174                 res_specified = 1;
175         }
176 done:
177
178         DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n",
179                 drm_get_connector_name(connector), xres, yres,
180                 (refresh) ? refresh : 60, (rb) ? " reduced blanking" :
181                 "", (margins) ? " with margins" : "", (interlace) ?
182                 " interlaced" : "");
183
184         if (force) {
185                 const char *s;
186                 switch (force) {
187                 case DRM_FORCE_OFF: s = "OFF"; break;
188                 case DRM_FORCE_ON_DIGITAL: s = "ON - dig"; break;
189                 default:
190                 case DRM_FORCE_ON: s = "ON"; break;
191                 }
192
193                 DRM_INFO("forcing %s connector %s\n",
194                          drm_get_connector_name(connector), s);
195                 connector->force = force;
196         }
197
198         if (res_specified) {
199                 cmdline_mode->specified = true;
200                 cmdline_mode->xres = xres;
201                 cmdline_mode->yres = yres;
202         }
203
204         if (refresh_specified) {
205                 cmdline_mode->refresh_specified = true;
206                 cmdline_mode->refresh = refresh;
207         }
208
209         if (bpp_specified) {
210                 cmdline_mode->bpp_specified = true;
211                 cmdline_mode->bpp = bpp;
212         }
213         cmdline_mode->rb = rb ? true : false;
214         cmdline_mode->cvt = cvt  ? true : false;
215         cmdline_mode->interlace = interlace ? true : false;
216
217         return true;
218 }
219
220 int drm_fb_helper_parse_command_line(struct drm_device *dev)
221 {
222         struct drm_connector *connector;
223
224         list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
225                 char *option = NULL;
226
227                 /* do something on return - turn off connector maybe */
228                 if (fb_get_options(drm_get_connector_name(connector), &option))
229                         continue;
230
231                 drm_fb_helper_connector_parse_command_line(connector, option);
232         }
233         return 0;
234 }
235
236 bool drm_fb_helper_force_kernel_mode(void)
237 {
238         int i = 0;
239         bool ret, error = false;
240         struct drm_fb_helper *helper;
241
242         if (list_empty(&kernel_fb_helper_list))
243                 return false;
244
245         list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) {
246                 for (i = 0; i < helper->crtc_count; i++) {
247                         struct drm_mode_set *mode_set = &helper->crtc_info[i].mode_set;
248                         ret = drm_crtc_helper_set_config(mode_set);
249                         if (ret)
250                                 error = true;
251                 }
252         }
253         return error;
254 }
255
256 int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed,
257                         void *panic_str)
258 {
259         DRM_ERROR("panic occurred, switching back to text console\n");
260         return drm_fb_helper_force_kernel_mode();
261         return 0;
262 }
263 EXPORT_SYMBOL(drm_fb_helper_panic);
264
265 static struct notifier_block paniced = {
266         .notifier_call = drm_fb_helper_panic,
267 };
268
269 /**
270  * drm_fb_helper_restore - restore the framebuffer console (kernel) config
271  *
272  * Restore's the kernel's fbcon mode, used for lastclose & panic paths.
273  */
274 void drm_fb_helper_restore(void)
275 {
276         bool ret;
277         ret = drm_fb_helper_force_kernel_mode();
278         if (ret == true)
279                 DRM_ERROR("Failed to restore crtc configuration\n");
280 }
281 EXPORT_SYMBOL(drm_fb_helper_restore);
282
283 static void drm_fb_helper_restore_work_fn(struct work_struct *ignored)
284 {
285         drm_fb_helper_restore();
286 }
287 static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn);
288
289 static void drm_fb_helper_sysrq(int dummy1, struct tty_struct *dummy3)
290 {
291         schedule_work(&drm_fb_helper_restore_work);
292 }
293
294 static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = {
295         .handler = drm_fb_helper_sysrq,
296         .help_msg = "force-fb(V)",
297         .action_msg = "Restore framebuffer console",
298 };
299
300 static void drm_fb_helper_on(struct fb_info *info)
301 {
302         struct drm_fb_helper *fb_helper = info->par;
303         struct drm_device *dev = fb_helper->dev;
304         struct drm_crtc *crtc;
305         struct drm_encoder *encoder;
306         int i;
307
308         /*
309          * For each CRTC in this fb, turn the crtc on then,
310          * find all associated encoders and turn them on.
311          */
312         for (i = 0; i < fb_helper->crtc_count; i++) {
313                 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
314                         struct drm_crtc_helper_funcs *crtc_funcs =
315                                 crtc->helper_private;
316
317                         /* Only mess with CRTCs in this fb */
318                         if (crtc->base.id != fb_helper->crtc_info[i].crtc_id ||
319                             !crtc->enabled)
320                                 continue;
321
322                         mutex_lock(&dev->mode_config.mutex);
323                         crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
324                         mutex_unlock(&dev->mode_config.mutex);
325
326                         /* Found a CRTC on this fb, now find encoders */
327                         list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
328                                 if (encoder->crtc == crtc) {
329                                         struct drm_encoder_helper_funcs *encoder_funcs;
330
331                                         encoder_funcs = encoder->helper_private;
332                                         mutex_lock(&dev->mode_config.mutex);
333                                         encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON);
334                                         mutex_unlock(&dev->mode_config.mutex);
335                                 }
336                         }
337                 }
338         }
339 }
340
341 static void drm_fb_helper_off(struct fb_info *info, int dpms_mode)
342 {
343         struct drm_fb_helper *fb_helper = info->par;
344         struct drm_device *dev = fb_helper->dev;
345         struct drm_crtc *crtc;
346         struct drm_encoder *encoder;
347         int i;
348
349         /*
350          * For each CRTC in this fb, find all associated encoders
351          * and turn them off, then turn off the CRTC.
352          */
353         for (i = 0; i < fb_helper->crtc_count; i++) {
354                 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
355                         struct drm_crtc_helper_funcs *crtc_funcs =
356                                 crtc->helper_private;
357
358                         /* Only mess with CRTCs in this fb */
359                         if (crtc->base.id != fb_helper->crtc_info[i].crtc_id ||
360                             !crtc->enabled)
361                                 continue;
362
363                         /* Found a CRTC on this fb, now find encoders */
364                         list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
365                                 if (encoder->crtc == crtc) {
366                                         struct drm_encoder_helper_funcs *encoder_funcs;
367
368                                         encoder_funcs = encoder->helper_private;
369                                         mutex_lock(&dev->mode_config.mutex);
370                                         encoder_funcs->dpms(encoder, dpms_mode);
371                                         mutex_unlock(&dev->mode_config.mutex);
372                                 }
373                         }
374                         if (dpms_mode == DRM_MODE_DPMS_OFF) {
375                                 mutex_lock(&dev->mode_config.mutex);
376                                 crtc_funcs->dpms(crtc, dpms_mode);
377                                 mutex_unlock(&dev->mode_config.mutex);
378                         }
379                 }
380         }
381 }
382
383 int drm_fb_helper_blank(int blank, struct fb_info *info)
384 {
385         switch (blank) {
386         case FB_BLANK_UNBLANK:
387                 drm_fb_helper_on(info);
388                 break;
389         case FB_BLANK_NORMAL:
390                 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
391                 break;
392         case FB_BLANK_HSYNC_SUSPEND:
393                 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
394                 break;
395         case FB_BLANK_VSYNC_SUSPEND:
396                 drm_fb_helper_off(info, DRM_MODE_DPMS_SUSPEND);
397                 break;
398         case FB_BLANK_POWERDOWN:
399                 drm_fb_helper_off(info, DRM_MODE_DPMS_OFF);
400                 break;
401         }
402         return 0;
403 }
404 EXPORT_SYMBOL(drm_fb_helper_blank);
405
406 static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
407 {
408         int i;
409
410         for (i = 0; i < helper->crtc_count; i++)
411                 kfree(helper->crtc_info[i].mode_set.connectors);
412         kfree(helper->crtc_info);
413 }
414
415 int drm_fb_helper_init_crtc_count(struct drm_fb_helper *helper, int crtc_count, int max_conn_count)
416 {
417         struct drm_device *dev = helper->dev;
418         struct drm_crtc *crtc;
419         int ret = 0;
420         int i;
421
422         helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL);
423         if (!helper->crtc_info)
424                 return -ENOMEM;
425
426         helper->crtc_count = crtc_count;
427
428         for (i = 0; i < crtc_count; i++) {
429                 helper->crtc_info[i].mode_set.connectors =
430                         kcalloc(max_conn_count,
431                                 sizeof(struct drm_connector *),
432                                 GFP_KERNEL);
433
434                 if (!helper->crtc_info[i].mode_set.connectors) {
435                         ret = -ENOMEM;
436                         goto out_free;
437                 }
438                 helper->crtc_info[i].mode_set.num_connectors = 0;
439         }
440
441         i = 0;
442         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
443                 helper->crtc_info[i].crtc_id = crtc->base.id;
444                 helper->crtc_info[i].mode_set.crtc = crtc;
445                 i++;
446         }
447         helper->conn_limit = max_conn_count;
448         return 0;
449 out_free:
450         drm_fb_helper_crtc_free(helper);
451         return -ENOMEM;
452 }
453 EXPORT_SYMBOL(drm_fb_helper_init_crtc_count);
454
455 int drm_fb_helper_setcolreg(unsigned regno,
456                             unsigned red,
457                             unsigned green,
458                             unsigned blue,
459                             unsigned transp,
460                             struct fb_info *info)
461 {
462         struct drm_fb_helper *fb_helper = info->par;
463         struct drm_device *dev = fb_helper->dev;
464         struct drm_crtc *crtc;
465         int i;
466
467         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
468                 struct drm_framebuffer *fb = fb_helper->fb;
469
470                 for (i = 0; i < fb_helper->crtc_count; i++) {
471                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
472                                 break;
473                 }
474                 if (i == fb_helper->crtc_count)
475                         continue;
476
477                 if (regno > 255)
478                         return 1;
479
480                 if (fb->depth == 8) {
481                         fb_helper->funcs->gamma_set(crtc, red, green, blue, regno);
482                         return 0;
483                 }
484
485                 if (regno < 16) {
486                         switch (fb->depth) {
487                         case 15:
488                                 fb->pseudo_palette[regno] = ((red & 0xf800) >> 1) |
489                                         ((green & 0xf800) >>  6) |
490                                         ((blue & 0xf800) >> 11);
491                                 break;
492                         case 16:
493                                 fb->pseudo_palette[regno] = (red & 0xf800) |
494                                         ((green & 0xfc00) >>  5) |
495                                         ((blue  & 0xf800) >> 11);
496                                 break;
497                         case 24:
498                         case 32:
499                                 fb->pseudo_palette[regno] =
500                                         (((red >> 8) & 0xff) << info->var.red.offset) |
501                                         (((green >> 8) & 0xff) << info->var.green.offset) |
502                                         (((blue >> 8) & 0xff) << info->var.blue.offset);
503                                 break;
504                         }
505                 }
506         }
507         return 0;
508 }
509 EXPORT_SYMBOL(drm_fb_helper_setcolreg);
510
511 int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
512                             struct fb_info *info)
513 {
514         struct drm_fb_helper *fb_helper = info->par;
515         struct drm_framebuffer *fb = fb_helper->fb;
516         int depth;
517
518         if (var->pixclock == -1 || !var->pixclock)
519                 return -EINVAL;
520
521         /* Need to resize the fb object !!! */
522         if (var->xres > fb->width || var->yres > fb->height) {
523                 DRM_ERROR("Requested width/height is greater than current fb "
524                            "object %dx%d > %dx%d\n", var->xres, var->yres,
525                            fb->width, fb->height);
526                 DRM_ERROR("Need resizing code.\n");
527                 return -EINVAL;
528         }
529
530         switch (var->bits_per_pixel) {
531         case 16:
532                 depth = (var->green.length == 6) ? 16 : 15;
533                 break;
534         case 32:
535                 depth = (var->transp.length > 0) ? 32 : 24;
536                 break;
537         default:
538                 depth = var->bits_per_pixel;
539                 break;
540         }
541
542         switch (depth) {
543         case 8:
544                 var->red.offset = 0;
545                 var->green.offset = 0;
546                 var->blue.offset = 0;
547                 var->red.length = 8;
548                 var->green.length = 8;
549                 var->blue.length = 8;
550                 var->transp.length = 0;
551                 var->transp.offset = 0;
552                 break;
553         case 15:
554                 var->red.offset = 10;
555                 var->green.offset = 5;
556                 var->blue.offset = 0;
557                 var->red.length = 5;
558                 var->green.length = 5;
559                 var->blue.length = 5;
560                 var->transp.length = 1;
561                 var->transp.offset = 15;
562                 break;
563         case 16:
564                 var->red.offset = 11;
565                 var->green.offset = 5;
566                 var->blue.offset = 0;
567                 var->red.length = 5;
568                 var->green.length = 6;
569                 var->blue.length = 5;
570                 var->transp.length = 0;
571                 var->transp.offset = 0;
572                 break;
573         case 24:
574                 var->red.offset = 16;
575                 var->green.offset = 8;
576                 var->blue.offset = 0;
577                 var->red.length = 8;
578                 var->green.length = 8;
579                 var->blue.length = 8;
580                 var->transp.length = 0;
581                 var->transp.offset = 0;
582                 break;
583         case 32:
584                 var->red.offset = 16;
585                 var->green.offset = 8;
586                 var->blue.offset = 0;
587                 var->red.length = 8;
588                 var->green.length = 8;
589                 var->blue.length = 8;
590                 var->transp.length = 8;
591                 var->transp.offset = 24;
592                 break;
593         default:
594                 return -EINVAL;
595         }
596         return 0;
597 }
598 EXPORT_SYMBOL(drm_fb_helper_check_var);
599
600 /* this will let fbcon do the mode init */
601 int drm_fb_helper_set_par(struct fb_info *info)
602 {
603         struct drm_fb_helper *fb_helper = info->par;
604         struct drm_device *dev = fb_helper->dev;
605         struct fb_var_screeninfo *var = &info->var;
606         struct drm_crtc *crtc;
607         int ret;
608         int i;
609
610         if (var->pixclock != -1) {
611                 DRM_ERROR("PIXEL CLCOK SET\n");
612                 return -EINVAL;
613         }
614
615         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
616
617                 for (i = 0; i < fb_helper->crtc_count; i++) {
618                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
619                                 break;
620                 }
621                 if (i == fb_helper->crtc_count)
622                         continue;
623
624                 if (crtc->fb == fb_helper->crtc_info[i].mode_set.fb) {
625                         mutex_lock(&dev->mode_config.mutex);
626                         ret = crtc->funcs->set_config(&fb_helper->crtc_info->mode_set);
627                         mutex_unlock(&dev->mode_config.mutex);
628                         if (ret)
629                                 return ret;
630                 }
631         }
632         return 0;
633 }
634 EXPORT_SYMBOL(drm_fb_helper_set_par);
635
636 int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
637                               struct fb_info *info)
638 {
639         struct drm_fb_helper *fb_helper = info->par;
640         struct drm_device *dev = fb_helper->dev;
641         struct drm_mode_set *modeset;
642         struct drm_crtc *crtc;
643         int ret = 0;
644         int i;
645
646         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
647                 for (i = 0; i < fb_helper->crtc_count; i++) {
648                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
649                                 break;
650                 }
651
652                 if (i == fb_helper->crtc_count)
653                         continue;
654
655                 modeset = &fb_helper->crtc_info[i].mode_set;
656
657                 modeset->x = var->xoffset;
658                 modeset->y = var->yoffset;
659
660                 if (modeset->num_connectors) {
661                         mutex_lock(&dev->mode_config.mutex);
662                         ret = crtc->funcs->set_config(modeset);
663                         mutex_unlock(&dev->mode_config.mutex);
664                         if (!ret) {
665                                 info->var.xoffset = var->xoffset;
666                                 info->var.yoffset = var->yoffset;
667                         }
668                 }
669         }
670         return ret;
671 }
672 EXPORT_SYMBOL(drm_fb_helper_pan_display);
673
674 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
675                                   int (*fb_create)(struct drm_device *dev,
676                                                    uint32_t fb_width,
677                                                    uint32_t fb_height,
678                                                    uint32_t surface_width,
679                                                    uint32_t surface_height,
680                                                    uint32_t surface_depth,
681                                                    uint32_t surface_bpp,
682                                                    struct drm_framebuffer **fb_ptr))
683 {
684         struct drm_crtc *crtc;
685         struct drm_connector *connector;
686         unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
687         unsigned int surface_width = 0, surface_height = 0;
688         int new_fb = 0;
689         int crtc_count = 0;
690         int ret, i, conn_count = 0;
691         struct fb_info *info;
692         struct drm_framebuffer *fb;
693         struct drm_mode_set *modeset = NULL;
694         struct drm_fb_helper *fb_helper;
695         uint32_t surface_depth = 24, surface_bpp = 32;
696
697         /* first up get a count of crtcs now in use and new min/maxes width/heights */
698         list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
699                 struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
700
701                 struct drm_fb_helper_cmdline_mode *cmdline_mode;
702
703                 if (!fb_help_conn)
704                         continue;
705                 
706                 cmdline_mode = &fb_help_conn->cmdline_mode;
707
708                 if (cmdline_mode->bpp_specified) {
709                         switch (cmdline_mode->bpp) {
710                         case 8:
711                                 surface_depth = surface_bpp = 8;
712                                 break;
713                         case 15:
714                                 surface_depth = 15;
715                                 surface_bpp = 16;
716                                 break;
717                         case 16:
718                                 surface_depth = surface_bpp = 16;
719                                 break;
720                         case 24:
721                                 surface_depth = surface_bpp = 24;
722                                 break;
723                         case 32:
724                                 surface_depth = 24;
725                                 surface_bpp = 32;
726                                 break;
727                         }
728                         break;
729                 }
730         }
731
732         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
733                 if (drm_helper_crtc_in_use(crtc)) {
734                         if (crtc->desired_mode) {
735                                 if (crtc->desired_mode->hdisplay < fb_width)
736                                         fb_width = crtc->desired_mode->hdisplay;
737
738                                 if (crtc->desired_mode->vdisplay < fb_height)
739                                         fb_height = crtc->desired_mode->vdisplay;
740
741                                 if (crtc->desired_mode->hdisplay > surface_width)
742                                         surface_width = crtc->desired_mode->hdisplay;
743
744                                 if (crtc->desired_mode->vdisplay > surface_height)
745                                         surface_height = crtc->desired_mode->vdisplay;
746                         }
747                         crtc_count++;
748                 }
749         }
750
751         if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
752                 /* hmm everyone went away - assume VGA cable just fell out
753                    and will come back later. */
754                 return 0;
755         }
756
757         /* do we have an fb already? */
758         if (list_empty(&dev->mode_config.fb_kernel_list)) {
759                 ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
760                                    surface_height, surface_depth, surface_bpp,
761                                    &fb);
762                 if (ret)
763                         return -EINVAL;
764                 new_fb = 1;
765         } else {
766                 fb = list_first_entry(&dev->mode_config.fb_kernel_list,
767                                       struct drm_framebuffer, filp_head);
768
769                 /* if someone hotplugs something bigger than we have already allocated, we are pwned.
770                    As really we can't resize an fbdev that is in the wild currently due to fbdev
771                    not really being designed for the lower layers moving stuff around under it.
772                    - so in the grand style of things - punt. */
773                 if ((fb->width < surface_width) ||
774                     (fb->height < surface_height)) {
775                         DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
776                         return -EINVAL;
777                 }
778         }
779
780         info = fb->fbdev;
781         fb_helper = info->par;
782
783         crtc_count = 0;
784         /* okay we need to setup new connector sets in the crtcs */
785         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
786                 modeset = &fb_helper->crtc_info[crtc_count].mode_set;
787                 modeset->fb = fb;
788                 conn_count = 0;
789                 list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
790                         if (connector->encoder)
791                                 if (connector->encoder->crtc == modeset->crtc) {
792                                         modeset->connectors[conn_count] = connector;
793                                         conn_count++;
794                                         if (conn_count > fb_helper->conn_limit)
795                                                 BUG();
796                                 }
797                 }
798
799                 for (i = conn_count; i < fb_helper->conn_limit; i++)
800                         modeset->connectors[i] = NULL;
801
802                 modeset->crtc = crtc;
803                 crtc_count++;
804
805                 modeset->num_connectors = conn_count;
806                 if (modeset->crtc->desired_mode) {
807                         if (modeset->mode)
808                                 drm_mode_destroy(dev, modeset->mode);
809                         modeset->mode = drm_mode_duplicate(dev,
810                                                            modeset->crtc->desired_mode);
811                 }
812         }
813         fb_helper->crtc_count = crtc_count;
814         fb_helper->fb = fb;
815
816         if (new_fb) {
817                 info->var.pixclock = -1;
818                 if (register_framebuffer(info) < 0)
819                         return -EINVAL;
820         } else {
821                 drm_fb_helper_set_par(info);
822         }
823         printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
824                info->fix.id);
825
826         /* Switch back to kernel console on panic */
827         /* multi card linked list maybe */
828         if (list_empty(&kernel_fb_helper_list)) {
829                 printk(KERN_INFO "registered panic notifier\n");
830                 atomic_notifier_chain_register(&panic_notifier_list,
831                                                &paniced);
832                 register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
833         }
834         list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
835         return 0;
836 }
837 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
838
839 void drm_fb_helper_free(struct drm_fb_helper *helper)
840 {
841         list_del(&helper->kernel_fb_list);
842         if (list_empty(&kernel_fb_helper_list)) {
843                 printk(KERN_INFO "unregistered panic notifier\n");
844                 atomic_notifier_chain_unregister(&panic_notifier_list,
845                                                  &paniced);
846                 unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
847         }
848         drm_fb_helper_crtc_free(helper);
849 }
850 EXPORT_SYMBOL(drm_fb_helper_free);
851
852 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch)
853 {
854         info->fix.type = FB_TYPE_PACKED_PIXELS;
855         info->fix.visual = FB_VISUAL_TRUECOLOR;
856         info->fix.type_aux = 0;
857         info->fix.xpanstep = 1; /* doing it in hw */
858         info->fix.ypanstep = 1; /* doing it in hw */
859         info->fix.ywrapstep = 0;
860         info->fix.accel = FB_ACCEL_NONE;
861         info->fix.type_aux = 0;
862
863         info->fix.line_length = pitch;
864         return;
865 }
866 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
867
868 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
869                             uint32_t fb_width, uint32_t fb_height)
870 {
871         info->pseudo_palette = fb->pseudo_palette;
872         info->var.xres_virtual = fb->width;
873         info->var.yres_virtual = fb->height;
874         info->var.bits_per_pixel = fb->bits_per_pixel;
875         info->var.xoffset = 0;
876         info->var.yoffset = 0;
877         info->var.activate = FB_ACTIVATE_NOW;
878         info->var.height = -1;
879         info->var.width = -1;
880
881         switch (fb->depth) {
882         case 8:
883                 info->var.red.offset = 0;
884                 info->var.green.offset = 0;
885                 info->var.blue.offset = 0;
886                 info->var.red.length = 8; /* 8bit DAC */
887                 info->var.green.length = 8;
888                 info->var.blue.length = 8;
889                 info->var.transp.offset = 0;
890                 info->var.transp.length = 0;
891                 break;
892         case 15:
893                 info->var.red.offset = 10;
894                 info->var.green.offset = 5;
895                 info->var.blue.offset = 0;
896                 info->var.red.length = 5;
897                 info->var.green.length = 5;
898                 info->var.blue.length = 5;
899                 info->var.transp.offset = 15;
900                 info->var.transp.length = 1;
901                 break;
902         case 16:
903                 info->var.red.offset = 11;
904                 info->var.green.offset = 5;
905                 info->var.blue.offset = 0;
906                 info->var.red.length = 5;
907                 info->var.green.length = 6;
908                 info->var.blue.length = 5;
909                 info->var.transp.offset = 0;
910                 break;
911         case 24:
912                 info->var.red.offset = 16;
913                 info->var.green.offset = 8;
914                 info->var.blue.offset = 0;
915                 info->var.red.length = 8;
916                 info->var.green.length = 8;
917                 info->var.blue.length = 8;
918                 info->var.transp.offset = 0;
919                 info->var.transp.length = 0;
920                 break;
921         case 32:
922                 info->var.red.offset = 16;
923                 info->var.green.offset = 8;
924                 info->var.blue.offset = 0;
925                 info->var.red.length = 8;
926                 info->var.green.length = 8;
927                 info->var.blue.length = 8;
928                 info->var.transp.offset = 24;
929                 info->var.transp.length = 8;
930                 break;
931         default:
932                 break;
933         }
934
935         info->var.xres = fb_width;
936         info->var.yres = fb_height;
937 }
938 EXPORT_SYMBOL(drm_fb_helper_fill_var);