xref: /linux/drivers/gpu/drm/drm_fb_helper.c (revision 4c8f1cb266cba4d1052f524d04df839d8f732ace)
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_setcolreg(unsigned regno,
458 			    unsigned red,
459 			    unsigned green,
460 			    unsigned blue,
461 			    unsigned transp,
462 			    struct fb_info *info)
463 {
464 	struct drm_fb_helper *fb_helper = info->par;
465 	struct drm_device *dev = fb_helper->dev;
466 	struct drm_crtc *crtc;
467 	int i;
468 
469 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
470 		struct drm_framebuffer *fb = fb_helper->fb;
471 
472 		for (i = 0; i < fb_helper->crtc_count; i++) {
473 			if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
474 				break;
475 		}
476 		if (i == fb_helper->crtc_count)
477 			continue;
478 
479 		if (regno > 255)
480 			return 1;
481 
482 		if (fb->depth == 8) {
483 			fb_helper->funcs->gamma_set(crtc, red, green, blue, regno);
484 			return 0;
485 		}
486 
487 		if (regno < 16) {
488 			switch (fb->depth) {
489 			case 15:
490 				fb->pseudo_palette[regno] = ((red & 0xf800) >> 1) |
491 					((green & 0xf800) >>  6) |
492 					((blue & 0xf800) >> 11);
493 				break;
494 			case 16:
495 				fb->pseudo_palette[regno] = (red & 0xf800) |
496 					((green & 0xfc00) >>  5) |
497 					((blue  & 0xf800) >> 11);
498 				break;
499 			case 24:
500 			case 32:
501 				fb->pseudo_palette[regno] =
502 					(((red >> 8) & 0xff) << info->var.red.offset) |
503 					(((green >> 8) & 0xff) << info->var.green.offset) |
504 					(((blue >> 8) & 0xff) << info->var.blue.offset);
505 				break;
506 			}
507 		}
508 	}
509 	return 0;
510 }
511 EXPORT_SYMBOL(drm_fb_helper_setcolreg);
512 
513 int drm_fb_helper_check_var(struct fb_var_screeninfo *var,
514 			    struct fb_info *info)
515 {
516 	struct drm_fb_helper *fb_helper = info->par;
517 	struct drm_framebuffer *fb = fb_helper->fb;
518 	int depth;
519 
520 	if (var->pixclock == -1 || !var->pixclock)
521 		return -EINVAL;
522 
523 	/* Need to resize the fb object !!! */
524 	if (var->xres > fb->width || var->yres > fb->height) {
525 		DRM_ERROR("Requested width/height is greater than current fb "
526 			   "object %dx%d > %dx%d\n", var->xres, var->yres,
527 			   fb->width, fb->height);
528 		DRM_ERROR("Need resizing code.\n");
529 		return -EINVAL;
530 	}
531 
532 	switch (var->bits_per_pixel) {
533 	case 16:
534 		depth = (var->green.length == 6) ? 16 : 15;
535 		break;
536 	case 32:
537 		depth = (var->transp.length > 0) ? 32 : 24;
538 		break;
539 	default:
540 		depth = var->bits_per_pixel;
541 		break;
542 	}
543 
544 	switch (depth) {
545 	case 8:
546 		var->red.offset = 0;
547 		var->green.offset = 0;
548 		var->blue.offset = 0;
549 		var->red.length = 8;
550 		var->green.length = 8;
551 		var->blue.length = 8;
552 		var->transp.length = 0;
553 		var->transp.offset = 0;
554 		break;
555 	case 15:
556 		var->red.offset = 10;
557 		var->green.offset = 5;
558 		var->blue.offset = 0;
559 		var->red.length = 5;
560 		var->green.length = 5;
561 		var->blue.length = 5;
562 		var->transp.length = 1;
563 		var->transp.offset = 15;
564 		break;
565 	case 16:
566 		var->red.offset = 11;
567 		var->green.offset = 5;
568 		var->blue.offset = 0;
569 		var->red.length = 5;
570 		var->green.length = 6;
571 		var->blue.length = 5;
572 		var->transp.length = 0;
573 		var->transp.offset = 0;
574 		break;
575 	case 24:
576 		var->red.offset = 16;
577 		var->green.offset = 8;
578 		var->blue.offset = 0;
579 		var->red.length = 8;
580 		var->green.length = 8;
581 		var->blue.length = 8;
582 		var->transp.length = 0;
583 		var->transp.offset = 0;
584 		break;
585 	case 32:
586 		var->red.offset = 16;
587 		var->green.offset = 8;
588 		var->blue.offset = 0;
589 		var->red.length = 8;
590 		var->green.length = 8;
591 		var->blue.length = 8;
592 		var->transp.length = 8;
593 		var->transp.offset = 24;
594 		break;
595 	default:
596 		return -EINVAL;
597 	}
598 	return 0;
599 }
600 EXPORT_SYMBOL(drm_fb_helper_check_var);
601 
602 /* this will let fbcon do the mode init */
603 int drm_fb_helper_set_par(struct fb_info *info)
604 {
605 	struct drm_fb_helper *fb_helper = info->par;
606 	struct drm_device *dev = fb_helper->dev;
607 	struct fb_var_screeninfo *var = &info->var;
608 	struct drm_crtc *crtc;
609 	int ret;
610 	int i;
611 
612 	if (var->pixclock != -1) {
613 		DRM_ERROR("PIXEL CLCOK SET\n");
614 		return -EINVAL;
615 	}
616 
617 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
618 
619 		for (i = 0; i < fb_helper->crtc_count; i++) {
620 			if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
621 				break;
622 		}
623 		if (i == fb_helper->crtc_count)
624 			continue;
625 
626 		if (crtc->fb == fb_helper->crtc_info[i].mode_set.fb) {
627 			mutex_lock(&dev->mode_config.mutex);
628 			ret = crtc->funcs->set_config(&fb_helper->crtc_info->mode_set);
629 			mutex_unlock(&dev->mode_config.mutex);
630 			if (ret)
631 				return ret;
632 		}
633 	}
634 	return 0;
635 }
636 EXPORT_SYMBOL(drm_fb_helper_set_par);
637 
638 int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
639 			      struct fb_info *info)
640 {
641 	struct drm_fb_helper *fb_helper = info->par;
642 	struct drm_device *dev = fb_helper->dev;
643 	struct drm_mode_set *modeset;
644 	struct drm_crtc *crtc;
645 	int ret = 0;
646 	int i;
647 
648 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
649 		for (i = 0; i < fb_helper->crtc_count; i++) {
650 			if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
651 				break;
652 		}
653 
654 		if (i == fb_helper->crtc_count)
655 			continue;
656 
657 		modeset = &fb_helper->crtc_info[i].mode_set;
658 
659 		modeset->x = var->xoffset;
660 		modeset->y = var->yoffset;
661 
662 		if (modeset->num_connectors) {
663 			mutex_lock(&dev->mode_config.mutex);
664 			ret = crtc->funcs->set_config(modeset);
665 			mutex_unlock(&dev->mode_config.mutex);
666 			if (!ret) {
667 				info->var.xoffset = var->xoffset;
668 				info->var.yoffset = var->yoffset;
669 			}
670 		}
671 	}
672 	return ret;
673 }
674 EXPORT_SYMBOL(drm_fb_helper_pan_display);
675 
676 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
677 				  int (*fb_create)(struct drm_device *dev,
678 						   uint32_t fb_width,
679 						   uint32_t fb_height,
680 						   uint32_t surface_width,
681 						   uint32_t surface_height,
682 						   uint32_t surface_depth,
683 						   uint32_t surface_bpp,
684 						   struct drm_framebuffer **fb_ptr))
685 {
686 	struct drm_crtc *crtc;
687 	struct drm_connector *connector;
688 	unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
689 	unsigned int surface_width = 0, surface_height = 0;
690 	int new_fb = 0;
691 	int crtc_count = 0;
692 	int ret, i, conn_count = 0;
693 	struct fb_info *info;
694 	struct drm_framebuffer *fb;
695 	struct drm_mode_set *modeset = NULL;
696 	struct drm_fb_helper *fb_helper;
697 	uint32_t surface_depth = 24, surface_bpp = 32;
698 
699 	/* first up get a count of crtcs now in use and new min/maxes width/heights */
700 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
701 		struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
702 
703 		struct drm_fb_helper_cmdline_mode *cmdline_mode;
704 
705 		if (!fb_help_conn)
706 			continue;
707 
708 		cmdline_mode = &fb_help_conn->cmdline_mode;
709 
710 		if (cmdline_mode->bpp_specified) {
711 			switch (cmdline_mode->bpp) {
712 			case 8:
713 				surface_depth = surface_bpp = 8;
714 				break;
715 			case 15:
716 				surface_depth = 15;
717 				surface_bpp = 16;
718 				break;
719 			case 16:
720 				surface_depth = surface_bpp = 16;
721 				break;
722 			case 24:
723 				surface_depth = surface_bpp = 24;
724 				break;
725 			case 32:
726 				surface_depth = 24;
727 				surface_bpp = 32;
728 				break;
729 			}
730 			break;
731 		}
732 	}
733 
734 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
735 		if (drm_helper_crtc_in_use(crtc)) {
736 			if (crtc->desired_mode) {
737 				if (crtc->desired_mode->hdisplay < fb_width)
738 					fb_width = crtc->desired_mode->hdisplay;
739 
740 				if (crtc->desired_mode->vdisplay < fb_height)
741 					fb_height = crtc->desired_mode->vdisplay;
742 
743 				if (crtc->desired_mode->hdisplay > surface_width)
744 					surface_width = crtc->desired_mode->hdisplay;
745 
746 				if (crtc->desired_mode->vdisplay > surface_height)
747 					surface_height = crtc->desired_mode->vdisplay;
748 			}
749 			crtc_count++;
750 		}
751 	}
752 
753 	if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
754 		/* hmm everyone went away - assume VGA cable just fell out
755 		   and will come back later. */
756 		return 0;
757 	}
758 
759 	/* do we have an fb already? */
760 	if (list_empty(&dev->mode_config.fb_kernel_list)) {
761 		ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
762 				   surface_height, surface_depth, surface_bpp,
763 				   &fb);
764 		if (ret)
765 			return -EINVAL;
766 		new_fb = 1;
767 	} else {
768 		fb = list_first_entry(&dev->mode_config.fb_kernel_list,
769 				      struct drm_framebuffer, filp_head);
770 
771 		/* if someone hotplugs something bigger than we have already allocated, we are pwned.
772 		   As really we can't resize an fbdev that is in the wild currently due to fbdev
773 		   not really being designed for the lower layers moving stuff around under it.
774 		   - so in the grand style of things - punt. */
775 		if ((fb->width < surface_width) ||
776 		    (fb->height < surface_height)) {
777 			DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
778 			return -EINVAL;
779 		}
780 	}
781 
782 	info = fb->fbdev;
783 	fb_helper = info->par;
784 
785 	crtc_count = 0;
786 	/* okay we need to setup new connector sets in the crtcs */
787 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
788 		modeset = &fb_helper->crtc_info[crtc_count].mode_set;
789 		modeset->fb = fb;
790 		conn_count = 0;
791 		list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
792 			if (connector->encoder)
793 				if (connector->encoder->crtc == modeset->crtc) {
794 					modeset->connectors[conn_count] = connector;
795 					conn_count++;
796 					if (conn_count > fb_helper->conn_limit)
797 						BUG();
798 				}
799 		}
800 
801 		for (i = conn_count; i < fb_helper->conn_limit; i++)
802 			modeset->connectors[i] = NULL;
803 
804 		modeset->crtc = crtc;
805 		crtc_count++;
806 
807 		modeset->num_connectors = conn_count;
808 		if (modeset->crtc->desired_mode) {
809 			if (modeset->mode)
810 				drm_mode_destroy(dev, modeset->mode);
811 			modeset->mode = drm_mode_duplicate(dev,
812 							   modeset->crtc->desired_mode);
813 		}
814 	}
815 	fb_helper->crtc_count = crtc_count;
816 	fb_helper->fb = fb;
817 
818 	if (new_fb) {
819 		info->var.pixclock = -1;
820 		if (register_framebuffer(info) < 0)
821 			return -EINVAL;
822 	} else {
823 		drm_fb_helper_set_par(info);
824 	}
825 	printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
826 	       info->fix.id);
827 
828 	/* Switch back to kernel console on panic */
829 	/* multi card linked list maybe */
830 	if (list_empty(&kernel_fb_helper_list)) {
831 		printk(KERN_INFO "registered panic notifier\n");
832 		atomic_notifier_chain_register(&panic_notifier_list,
833 					       &paniced);
834 		register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
835 	}
836 	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
837 	return 0;
838 }
839 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
840 
841 void drm_fb_helper_free(struct drm_fb_helper *helper)
842 {
843 	list_del(&helper->kernel_fb_list);
844 	if (list_empty(&kernel_fb_helper_list)) {
845 		printk(KERN_INFO "unregistered panic notifier\n");
846 		atomic_notifier_chain_unregister(&panic_notifier_list,
847 						 &paniced);
848 		unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
849 	}
850 	drm_fb_helper_crtc_free(helper);
851 }
852 EXPORT_SYMBOL(drm_fb_helper_free);
853 
854 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch)
855 {
856 	info->fix.type = FB_TYPE_PACKED_PIXELS;
857 	info->fix.visual = FB_VISUAL_TRUECOLOR;
858 	info->fix.type_aux = 0;
859 	info->fix.xpanstep = 1; /* doing it in hw */
860 	info->fix.ypanstep = 1; /* doing it in hw */
861 	info->fix.ywrapstep = 0;
862 	info->fix.accel = FB_ACCEL_NONE;
863 	info->fix.type_aux = 0;
864 
865 	info->fix.line_length = pitch;
866 	return;
867 }
868 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
869 
870 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
871 			    uint32_t fb_width, uint32_t fb_height)
872 {
873 	info->pseudo_palette = fb->pseudo_palette;
874 	info->var.xres_virtual = fb->width;
875 	info->var.yres_virtual = fb->height;
876 	info->var.bits_per_pixel = fb->bits_per_pixel;
877 	info->var.xoffset = 0;
878 	info->var.yoffset = 0;
879 	info->var.activate = FB_ACTIVATE_NOW;
880 	info->var.height = -1;
881 	info->var.width = -1;
882 
883 	switch (fb->depth) {
884 	case 8:
885 		info->var.red.offset = 0;
886 		info->var.green.offset = 0;
887 		info->var.blue.offset = 0;
888 		info->var.red.length = 8; /* 8bit DAC */
889 		info->var.green.length = 8;
890 		info->var.blue.length = 8;
891 		info->var.transp.offset = 0;
892 		info->var.transp.length = 0;
893 		break;
894 	case 15:
895 		info->var.red.offset = 10;
896 		info->var.green.offset = 5;
897 		info->var.blue.offset = 0;
898 		info->var.red.length = 5;
899 		info->var.green.length = 5;
900 		info->var.blue.length = 5;
901 		info->var.transp.offset = 15;
902 		info->var.transp.length = 1;
903 		break;
904 	case 16:
905 		info->var.red.offset = 11;
906 		info->var.green.offset = 5;
907 		info->var.blue.offset = 0;
908 		info->var.red.length = 5;
909 		info->var.green.length = 6;
910 		info->var.blue.length = 5;
911 		info->var.transp.offset = 0;
912 		break;
913 	case 24:
914 		info->var.red.offset = 16;
915 		info->var.green.offset = 8;
916 		info->var.blue.offset = 0;
917 		info->var.red.length = 8;
918 		info->var.green.length = 8;
919 		info->var.blue.length = 8;
920 		info->var.transp.offset = 0;
921 		info->var.transp.length = 0;
922 		break;
923 	case 32:
924 		info->var.red.offset = 16;
925 		info->var.green.offset = 8;
926 		info->var.blue.offset = 0;
927 		info->var.red.length = 8;
928 		info->var.green.length = 8;
929 		info->var.blue.length = 8;
930 		info->var.transp.offset = 24;
931 		info->var.transp.length = 8;
932 		break;
933 	default:
934 		break;
935 	}
936 
937 	info->var.xres = fb_width;
938 	info->var.yres = fb_height;
939 }
940 EXPORT_SYMBOL(drm_fb_helper_fill_var);
941