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