drm/fb: add setcmap and fix 8-bit support.
[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 #ifdef CONFIG_MAGIC_SYSRQ
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 #endif
301
302 static void drm_fb_helper_on(struct fb_info *info)
303 {
304         struct drm_fb_helper *fb_helper = info->par;
305         struct drm_device *dev = fb_helper->dev;
306         struct drm_crtc *crtc;
307         struct drm_encoder *encoder;
308         int i;
309
310         /*
311          * For each CRTC in this fb, turn the crtc on then,
312          * find all associated encoders and turn them on.
313          */
314         for (i = 0; i < fb_helper->crtc_count; i++) {
315                 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
316                         struct drm_crtc_helper_funcs *crtc_funcs =
317                                 crtc->helper_private;
318
319                         /* Only mess with CRTCs in this fb */
320                         if (crtc->base.id != fb_helper->crtc_info[i].crtc_id ||
321                             !crtc->enabled)
322                                 continue;
323
324                         mutex_lock(&dev->mode_config.mutex);
325                         crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
326                         mutex_unlock(&dev->mode_config.mutex);
327
328                         /* Found a CRTC on this fb, now find encoders */
329                         list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
330                                 if (encoder->crtc == crtc) {
331                                         struct drm_encoder_helper_funcs *encoder_funcs;
332
333                                         encoder_funcs = encoder->helper_private;
334                                         mutex_lock(&dev->mode_config.mutex);
335                                         encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON);
336                                         mutex_unlock(&dev->mode_config.mutex);
337                                 }
338                         }
339                 }
340         }
341 }
342
343 static void drm_fb_helper_off(struct fb_info *info, int dpms_mode)
344 {
345         struct drm_fb_helper *fb_helper = info->par;
346         struct drm_device *dev = fb_helper->dev;
347         struct drm_crtc *crtc;
348         struct drm_encoder *encoder;
349         int i;
350
351         /*
352          * For each CRTC in this fb, find all associated encoders
353          * and turn them off, then turn off the CRTC.
354          */
355         for (i = 0; i < fb_helper->crtc_count; i++) {
356                 list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
357                         struct drm_crtc_helper_funcs *crtc_funcs =
358                                 crtc->helper_private;
359
360                         /* Only mess with CRTCs in this fb */
361                         if (crtc->base.id != fb_helper->crtc_info[i].crtc_id ||
362                             !crtc->enabled)
363                                 continue;
364
365                         /* Found a CRTC on this fb, now find encoders */
366                         list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
367                                 if (encoder->crtc == crtc) {
368                                         struct drm_encoder_helper_funcs *encoder_funcs;
369
370                                         encoder_funcs = encoder->helper_private;
371                                         mutex_lock(&dev->mode_config.mutex);
372                                         encoder_funcs->dpms(encoder, dpms_mode);
373                                         mutex_unlock(&dev->mode_config.mutex);
374                                 }
375                         }
376                         if (dpms_mode == DRM_MODE_DPMS_OFF) {
377                                 mutex_lock(&dev->mode_config.mutex);
378                                 crtc_funcs->dpms(crtc, dpms_mode);
379                                 mutex_unlock(&dev->mode_config.mutex);
380                         }
381                 }
382         }
383 }
384
385 int drm_fb_helper_blank(int blank, struct fb_info *info)
386 {
387         switch (blank) {
388         case FB_BLANK_UNBLANK:
389                 drm_fb_helper_on(info);
390                 break;
391         case FB_BLANK_NORMAL:
392                 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
393                 break;
394         case FB_BLANK_HSYNC_SUSPEND:
395                 drm_fb_helper_off(info, DRM_MODE_DPMS_STANDBY);
396                 break;
397         case FB_BLANK_VSYNC_SUSPEND:
398                 drm_fb_helper_off(info, DRM_MODE_DPMS_SUSPEND);
399                 break;
400         case FB_BLANK_POWERDOWN:
401                 drm_fb_helper_off(info, DRM_MODE_DPMS_OFF);
402                 break;
403         }
404         return 0;
405 }
406 EXPORT_SYMBOL(drm_fb_helper_blank);
407
408 static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper)
409 {
410         int i;
411
412         for (i = 0; i < helper->crtc_count; i++)
413                 kfree(helper->crtc_info[i].mode_set.connectors);
414         kfree(helper->crtc_info);
415 }
416
417 int drm_fb_helper_init_crtc_count(struct drm_fb_helper *helper, int crtc_count, int max_conn_count)
418 {
419         struct drm_device *dev = helper->dev;
420         struct drm_crtc *crtc;
421         int ret = 0;
422         int i;
423
424         helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL);
425         if (!helper->crtc_info)
426                 return -ENOMEM;
427
428         helper->crtc_count = crtc_count;
429
430         for (i = 0; i < crtc_count; i++) {
431                 helper->crtc_info[i].mode_set.connectors =
432                         kcalloc(max_conn_count,
433                                 sizeof(struct drm_connector *),
434                                 GFP_KERNEL);
435
436                 if (!helper->crtc_info[i].mode_set.connectors) {
437                         ret = -ENOMEM;
438                         goto out_free;
439                 }
440                 helper->crtc_info[i].mode_set.num_connectors = 0;
441         }
442
443         i = 0;
444         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
445                 helper->crtc_info[i].crtc_id = crtc->base.id;
446                 helper->crtc_info[i].mode_set.crtc = crtc;
447                 i++;
448         }
449         helper->conn_limit = max_conn_count;
450         return 0;
451 out_free:
452         drm_fb_helper_crtc_free(helper);
453         return -ENOMEM;
454 }
455 EXPORT_SYMBOL(drm_fb_helper_init_crtc_count);
456
457 int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info)
458 {
459         struct drm_fb_helper *fb_helper = info->par;
460         struct drm_device *dev = fb_helper->dev;
461         u16 *red, *green, *blue, *transp;
462         struct drm_crtc *crtc;
463         int i, rc = 0;
464         int start;
465
466         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
467                 struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
468                 for (i = 0; i < fb_helper->crtc_count; i++) {
469                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
470                                 break;
471                 }
472                 if (i == fb_helper->crtc_count)
473                         continue;
474
475                 red = cmap->red;
476                 green = cmap->green;
477                 blue = cmap->blue;
478                 transp = cmap->transp;
479                 start = cmap->start;
480
481                 for (i = 0; i < cmap->len; i++) {
482                         u16 hred, hgreen, hblue, htransp = 0xffff;
483
484                         hred = *red++;
485                         hgreen = *green++;
486                         hblue = *blue++;
487
488                         if (transp)
489                                 htransp = *transp++;
490
491                         fb_helper->funcs->gamma_set(crtc, hred, hgreen, hblue, start++);
492                 }
493                 crtc_funcs->load_lut(crtc);
494         }
495         return rc;
496 }
497 EXPORT_SYMBOL(drm_fb_helper_setcmap);
498
499 int drm_fb_helper_setcolreg(unsigned regno,
500                             unsigned red,
501                             unsigned green,
502                             unsigned blue,
503                             unsigned transp,
504                             struct fb_info *info)
505 {
506         struct drm_fb_helper *fb_helper = info->par;
507         struct drm_device *dev = fb_helper->dev;
508         struct drm_crtc *crtc;
509         int i;
510
511         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
512                 struct drm_framebuffer *fb = fb_helper->fb;
513
514                 for (i = 0; i < fb_helper->crtc_count; i++) {
515                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
516                                 break;
517                 }
518                 if (i == fb_helper->crtc_count)
519                         continue;
520
521                 if (regno > 255)
522                         return 1;
523
524                 if (fb->depth == 8) {
525                         fb_helper->funcs->gamma_set(crtc, red, green, blue, regno);
526                         return 0;
527                 }
528
529                 if (regno < 16) {
530                         u32 *pal = fb->pseudo_palette;
531                         switch (fb->depth) {
532                         case 15:
533                                 pal[regno] = ((red & 0xf800) >> 1) |
534                                         ((green & 0xf800) >>  6) |
535                                         ((blue & 0xf800) >> 11);
536                                 break;
537                         case 16:
538                                 pal[regno] = (red & 0xf800) |
539                                         ((green & 0xfc00) >>  5) |
540                                         ((blue  & 0xf800) >> 11);
541                                 break;
542                         case 24:
543                         case 32:
544                                 pal[regno] =
545                                         (((red >> 8) & 0xff) << info->var.red.offset) |
546                                         (((green >> 8) & 0xff) << info->var.green.offset) |
547                                         (((blue >> 8) & 0xff) << info->var.blue.offset);
548                                 break;
549                         }
550                 }
551         }
552         return 0;
553 }
554 EXPORT_SYMBOL(drm_fb_helper_setcolreg);
555
556 int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
557                             struct fb_info *info)
558 {
559         struct drm_fb_helper *fb_helper = info->par;
560         struct drm_framebuffer *fb = fb_helper->fb;
561         int depth;
562
563         if (var->pixclock == -1 || !var->pixclock)
564                 return -EINVAL;
565
566         /* Need to resize the fb object !!! */
567         if (var->xres > fb->width || var->yres > fb->height) {
568                 DRM_ERROR("Requested width/height is greater than current fb "
569                            "object %dx%d > %dx%d\n", var->xres, var->yres,
570                            fb->width, fb->height);
571                 DRM_ERROR("Need resizing code.\n");
572                 return -EINVAL;
573         }
574
575         switch (var->bits_per_pixel) {
576         case 16:
577                 depth = (var->green.length == 6) ? 16 : 15;
578                 break;
579         case 32:
580                 depth = (var->transp.length > 0) ? 32 : 24;
581                 break;
582         default:
583                 depth = var->bits_per_pixel;
584                 break;
585         }
586
587         switch (depth) {
588         case 8:
589                 var->red.offset = 0;
590                 var->green.offset = 0;
591                 var->blue.offset = 0;
592                 var->red.length = 8;
593                 var->green.length = 8;
594                 var->blue.length = 8;
595                 var->transp.length = 0;
596                 var->transp.offset = 0;
597                 break;
598         case 15:
599                 var->red.offset = 10;
600                 var->green.offset = 5;
601                 var->blue.offset = 0;
602                 var->red.length = 5;
603                 var->green.length = 5;
604                 var->blue.length = 5;
605                 var->transp.length = 1;
606                 var->transp.offset = 15;
607                 break;
608         case 16:
609                 var->red.offset = 11;
610                 var->green.offset = 5;
611                 var->blue.offset = 0;
612                 var->red.length = 5;
613                 var->green.length = 6;
614                 var->blue.length = 5;
615                 var->transp.length = 0;
616                 var->transp.offset = 0;
617                 break;
618         case 24:
619                 var->red.offset = 16;
620                 var->green.offset = 8;
621                 var->blue.offset = 0;
622                 var->red.length = 8;
623                 var->green.length = 8;
624                 var->blue.length = 8;
625                 var->transp.length = 0;
626                 var->transp.offset = 0;
627                 break;
628         case 32:
629                 var->red.offset = 16;
630                 var->green.offset = 8;
631                 var->blue.offset = 0;
632                 var->red.length = 8;
633                 var->green.length = 8;
634                 var->blue.length = 8;
635                 var->transp.length = 8;
636                 var->transp.offset = 24;
637                 break;
638         default:
639                 return -EINVAL;
640         }
641         return 0;
642 }
643 EXPORT_SYMBOL(drm_fb_helper_check_var);
644
645 /* this will let fbcon do the mode init */
646 int drm_fb_helper_set_par(struct fb_info *info)
647 {
648         struct drm_fb_helper *fb_helper = info->par;
649         struct drm_device *dev = fb_helper->dev;
650         struct fb_var_screeninfo *var = &info->var;
651         struct drm_crtc *crtc;
652         int ret;
653         int i;
654
655         if (var->pixclock != -1) {
656                 DRM_ERROR("PIXEL CLCOK SET\n");
657                 return -EINVAL;
658         }
659
660         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
661
662                 for (i = 0; i < fb_helper->crtc_count; i++) {
663                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
664                                 break;
665                 }
666                 if (i == fb_helper->crtc_count)
667                         continue;
668
669                 if (crtc->fb == fb_helper->crtc_info[i].mode_set.fb) {
670                         mutex_lock(&dev->mode_config.mutex);
671                         ret = crtc->funcs->set_config(&fb_helper->crtc_info->mode_set);
672                         mutex_unlock(&dev->mode_config.mutex);
673                         if (ret)
674                                 return ret;
675                 }
676         }
677         return 0;
678 }
679 EXPORT_SYMBOL(drm_fb_helper_set_par);
680
681 int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
682                               struct fb_info *info)
683 {
684         struct drm_fb_helper *fb_helper = info->par;
685         struct drm_device *dev = fb_helper->dev;
686         struct drm_mode_set *modeset;
687         struct drm_crtc *crtc;
688         int ret = 0;
689         int i;
690
691         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
692                 for (i = 0; i < fb_helper->crtc_count; i++) {
693                         if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
694                                 break;
695                 }
696
697                 if (i == fb_helper->crtc_count)
698                         continue;
699
700                 modeset = &fb_helper->crtc_info[i].mode_set;
701
702                 modeset->x = var->xoffset;
703                 modeset->y = var->yoffset;
704
705                 if (modeset->num_connectors) {
706                         mutex_lock(&dev->mode_config.mutex);
707                         ret = crtc->funcs->set_config(modeset);
708                         mutex_unlock(&dev->mode_config.mutex);
709                         if (!ret) {
710                                 info->var.xoffset = var->xoffset;
711                                 info->var.yoffset = var->yoffset;
712                         }
713                 }
714         }
715         return ret;
716 }
717 EXPORT_SYMBOL(drm_fb_helper_pan_display);
718
719 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
720                                   int (*fb_create)(struct drm_device *dev,
721                                                    uint32_t fb_width,
722                                                    uint32_t fb_height,
723                                                    uint32_t surface_width,
724                                                    uint32_t surface_height,
725                                                    uint32_t surface_depth,
726                                                    uint32_t surface_bpp,
727                                                    struct drm_framebuffer **fb_ptr))
728 {
729         struct drm_crtc *crtc;
730         struct drm_connector *connector;
731         unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
732         unsigned int surface_width = 0, surface_height = 0;
733         int new_fb = 0;
734         int crtc_count = 0;
735         int ret, i, conn_count = 0;
736         struct fb_info *info;
737         struct drm_framebuffer *fb;
738         struct drm_mode_set *modeset = NULL;
739         struct drm_fb_helper *fb_helper;
740         uint32_t surface_depth = 24, surface_bpp = 32;
741
742         /* first up get a count of crtcs now in use and new min/maxes width/heights */
743         list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
744                 struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
745
746                 struct drm_fb_helper_cmdline_mode *cmdline_mode;
747
748                 if (!fb_help_conn)
749                         continue;
750                 
751                 cmdline_mode = &fb_help_conn->cmdline_mode;
752
753                 if (cmdline_mode->bpp_specified) {
754                         switch (cmdline_mode->bpp) {
755                         case 8:
756                                 surface_depth = surface_bpp = 8;
757                                 break;
758                         case 15:
759                                 surface_depth = 15;
760                                 surface_bpp = 16;
761                                 break;
762                         case 16:
763                                 surface_depth = surface_bpp = 16;
764                                 break;
765                         case 24:
766                                 surface_depth = surface_bpp = 24;
767                                 break;
768                         case 32:
769                                 surface_depth = 24;
770                                 surface_bpp = 32;
771                                 break;
772                         }
773                         break;
774                 }
775         }
776
777         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
778                 if (drm_helper_crtc_in_use(crtc)) {
779                         if (crtc->desired_mode) {
780                                 if (crtc->desired_mode->hdisplay < fb_width)
781                                         fb_width = crtc->desired_mode->hdisplay;
782
783                                 if (crtc->desired_mode->vdisplay < fb_height)
784                                         fb_height = crtc->desired_mode->vdisplay;
785
786                                 if (crtc->desired_mode->hdisplay > surface_width)
787                                         surface_width = crtc->desired_mode->hdisplay;
788
789                                 if (crtc->desired_mode->vdisplay > surface_height)
790                                         surface_height = crtc->desired_mode->vdisplay;
791                         }
792                         crtc_count++;
793                 }
794         }
795
796         if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
797                 /* hmm everyone went away - assume VGA cable just fell out
798                    and will come back later. */
799                 return 0;
800         }
801
802         /* do we have an fb already? */
803         if (list_empty(&dev->mode_config.fb_kernel_list)) {
804                 ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
805                                    surface_height, surface_depth, surface_bpp,
806                                    &fb);
807                 if (ret)
808                         return -EINVAL;
809                 new_fb = 1;
810         } else {
811                 fb = list_first_entry(&dev->mode_config.fb_kernel_list,
812                                       struct drm_framebuffer, filp_head);
813
814                 /* if someone hotplugs something bigger than we have already allocated, we are pwned.
815                    As really we can't resize an fbdev that is in the wild currently due to fbdev
816                    not really being designed for the lower layers moving stuff around under it.
817                    - so in the grand style of things - punt. */
818                 if ((fb->width < surface_width) ||
819                     (fb->height < surface_height)) {
820                         DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
821                         return -EINVAL;
822                 }
823         }
824
825         info = fb->fbdev;
826         fb_helper = info->par;
827
828         crtc_count = 0;
829         /* okay we need to setup new connector sets in the crtcs */
830         list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
831                 modeset = &fb_helper->crtc_info[crtc_count].mode_set;
832                 modeset->fb = fb;
833                 conn_count = 0;
834                 list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
835                         if (connector->encoder)
836                                 if (connector->encoder->crtc == modeset->crtc) {
837                                         modeset->connectors[conn_count] = connector;
838                                         conn_count++;
839                                         if (conn_count > fb_helper->conn_limit)
840                                                 BUG();
841                                 }
842                 }
843
844                 for (i = conn_count; i < fb_helper->conn_limit; i++)
845                         modeset->connectors[i] = NULL;
846
847                 modeset->crtc = crtc;
848                 crtc_count++;
849
850                 modeset->num_connectors = conn_count;
851                 if (modeset->crtc->desired_mode) {
852                         if (modeset->mode)
853                                 drm_mode_destroy(dev, modeset->mode);
854                         modeset->mode = drm_mode_duplicate(dev,
855                                                            modeset->crtc->desired_mode);
856                 }
857         }
858         fb_helper->crtc_count = crtc_count;
859         fb_helper->fb = fb;
860
861         if (new_fb) {
862                 info->var.pixclock = -1;
863                 if (register_framebuffer(info) < 0)
864                         return -EINVAL;
865         } else {
866                 drm_fb_helper_set_par(info);
867         }
868         printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
869                info->fix.id);
870
871         /* Switch back to kernel console on panic */
872         /* multi card linked list maybe */
873         if (list_empty(&kernel_fb_helper_list)) {
874                 printk(KERN_INFO "registered panic notifier\n");
875                 atomic_notifier_chain_register(&panic_notifier_list,
876                                                &paniced);
877                 register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
878         }
879         list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
880         return 0;
881 }
882 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
883
884 void drm_fb_helper_free(struct drm_fb_helper *helper)
885 {
886         list_del(&helper->kernel_fb_list);
887         if (list_empty(&kernel_fb_helper_list)) {
888                 printk(KERN_INFO "unregistered panic notifier\n");
889                 atomic_notifier_chain_unregister(&panic_notifier_list,
890                                                  &paniced);
891                 unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
892         }
893         drm_fb_helper_crtc_free(helper);
894 }
895 EXPORT_SYMBOL(drm_fb_helper_free);
896
897 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
898                             uint32_t depth)
899 {
900         info->fix.type = FB_TYPE_PACKED_PIXELS;
901         info->fix.visual = depth == 8 ? FB_VISUAL_PSEUDOCOLOR :
902                 FB_VISUAL_TRUECOLOR;
903         info->fix.type_aux = 0;
904         info->fix.xpanstep = 1; /* doing it in hw */
905         info->fix.ypanstep = 1; /* doing it in hw */
906         info->fix.ywrapstep = 0;
907         info->fix.accel = FB_ACCEL_NONE;
908         info->fix.type_aux = 0;
909
910         info->fix.line_length = pitch;
911         return;
912 }
913 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
914
915 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
916                             uint32_t fb_width, uint32_t fb_height)
917 {
918         info->pseudo_palette = fb->pseudo_palette;
919         info->var.xres_virtual = fb->width;
920         info->var.yres_virtual = fb->height;
921         info->var.bits_per_pixel = fb->bits_per_pixel;
922         info->var.xoffset = 0;
923         info->var.yoffset = 0;
924         info->var.activate = FB_ACTIVATE_NOW;
925         info->var.height = -1;
926         info->var.width = -1;
927
928         switch (fb->depth) {
929         case 8:
930                 info->var.red.offset = 0;
931                 info->var.green.offset = 0;
932                 info->var.blue.offset = 0;
933                 info->var.red.length = 8; /* 8bit DAC */
934                 info->var.green.length = 8;
935                 info->var.blue.length = 8;
936                 info->var.transp.offset = 0;
937                 info->var.transp.length = 0;
938                 break;
939         case 15:
940                 info->var.red.offset = 10;
941                 info->var.green.offset = 5;
942                 info->var.blue.offset = 0;
943                 info->var.red.length = 5;
944                 info->var.green.length = 5;
945                 info->var.blue.length = 5;
946                 info->var.transp.offset = 15;
947                 info->var.transp.length = 1;
948                 break;
949         case 16:
950                 info->var.red.offset = 11;
951                 info->var.green.offset = 5;
952                 info->var.blue.offset = 0;
953                 info->var.red.length = 5;
954                 info->var.green.length = 6;
955                 info->var.blue.length = 5;
956                 info->var.transp.offset = 0;
957                 break;
958         case 24:
959                 info->var.red.offset = 16;
960                 info->var.green.offset = 8;
961                 info->var.blue.offset = 0;
962                 info->var.red.length = 8;
963                 info->var.green.length = 8;
964                 info->var.blue.length = 8;
965                 info->var.transp.offset = 0;
966                 info->var.transp.length = 0;
967                 break;
968         case 32:
969                 info->var.red.offset = 16;
970                 info->var.green.offset = 8;
971                 info->var.blue.offset = 0;
972                 info->var.red.length = 8;
973                 info->var.green.length = 8;
974                 info->var.blue.length = 8;
975                 info->var.transp.offset = 24;
976                 info->var.transp.length = 8;
977                 break;
978         default:
979                 break;
980         }
981
982         info->var.xres = fb_width;
983         info->var.yres = fb_height;
984 }
985 EXPORT_SYMBOL(drm_fb_helper_fill_var);