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