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