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