xref: /openbmc/linux/drivers/gpu/drm/drm_fb_helper.c (revision b6dcefde)
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_STANDBY);
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->bits_per_pixel > fb->bits_per_pixel || var->xres > fb->width || var->yres > fb->height) {
610 		DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb "
611 			  "object %dx%d-%d > %dx%d-%d\n", var->xres, var->yres, var->bits_per_pixel,
612 			  fb->width, fb->height, fb->bits_per_pixel);
613 		return -EINVAL;
614 	}
615 
616 	switch (var->bits_per_pixel) {
617 	case 16:
618 		depth = (var->green.length == 6) ? 16 : 15;
619 		break;
620 	case 32:
621 		depth = (var->transp.length > 0) ? 32 : 24;
622 		break;
623 	default:
624 		depth = var->bits_per_pixel;
625 		break;
626 	}
627 
628 	switch (depth) {
629 	case 8:
630 		var->red.offset = 0;
631 		var->green.offset = 0;
632 		var->blue.offset = 0;
633 		var->red.length = 8;
634 		var->green.length = 8;
635 		var->blue.length = 8;
636 		var->transp.length = 0;
637 		var->transp.offset = 0;
638 		break;
639 	case 15:
640 		var->red.offset = 10;
641 		var->green.offset = 5;
642 		var->blue.offset = 0;
643 		var->red.length = 5;
644 		var->green.length = 5;
645 		var->blue.length = 5;
646 		var->transp.length = 1;
647 		var->transp.offset = 15;
648 		break;
649 	case 16:
650 		var->red.offset = 11;
651 		var->green.offset = 5;
652 		var->blue.offset = 0;
653 		var->red.length = 5;
654 		var->green.length = 6;
655 		var->blue.length = 5;
656 		var->transp.length = 0;
657 		var->transp.offset = 0;
658 		break;
659 	case 24:
660 		var->red.offset = 16;
661 		var->green.offset = 8;
662 		var->blue.offset = 0;
663 		var->red.length = 8;
664 		var->green.length = 8;
665 		var->blue.length = 8;
666 		var->transp.length = 0;
667 		var->transp.offset = 0;
668 		break;
669 	case 32:
670 		var->red.offset = 16;
671 		var->green.offset = 8;
672 		var->blue.offset = 0;
673 		var->red.length = 8;
674 		var->green.length = 8;
675 		var->blue.length = 8;
676 		var->transp.length = 8;
677 		var->transp.offset = 24;
678 		break;
679 	default:
680 		return -EINVAL;
681 	}
682 	return 0;
683 }
684 EXPORT_SYMBOL(drm_fb_helper_check_var);
685 
686 /* this will let fbcon do the mode init */
687 int drm_fb_helper_set_par(struct fb_info *info)
688 {
689 	struct drm_fb_helper *fb_helper = info->par;
690 	struct drm_device *dev = fb_helper->dev;
691 	struct fb_var_screeninfo *var = &info->var;
692 	struct drm_crtc *crtc;
693 	int ret;
694 	int i;
695 
696 	if (var->pixclock != 0) {
697 		DRM_ERROR("PIXEL CLCOK SET\n");
698 		return -EINVAL;
699 	}
700 
701 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
702 
703 		for (i = 0; i < fb_helper->crtc_count; i++) {
704 			if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
705 				break;
706 		}
707 		if (i == fb_helper->crtc_count)
708 			continue;
709 
710 		if (crtc->fb == fb_helper->crtc_info[i].mode_set.fb) {
711 			mutex_lock(&dev->mode_config.mutex);
712 			ret = crtc->funcs->set_config(&fb_helper->crtc_info[i].mode_set);
713 			mutex_unlock(&dev->mode_config.mutex);
714 			if (ret)
715 				return ret;
716 		}
717 	}
718 	return 0;
719 }
720 EXPORT_SYMBOL(drm_fb_helper_set_par);
721 
722 int drm_fb_helper_pan_display(struct fb_var_screeninfo *var,
723 			      struct fb_info *info)
724 {
725 	struct drm_fb_helper *fb_helper = info->par;
726 	struct drm_device *dev = fb_helper->dev;
727 	struct drm_mode_set *modeset;
728 	struct drm_crtc *crtc;
729 	int ret = 0;
730 	int i;
731 
732 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
733 		for (i = 0; i < fb_helper->crtc_count; i++) {
734 			if (crtc->base.id == fb_helper->crtc_info[i].crtc_id)
735 				break;
736 		}
737 
738 		if (i == fb_helper->crtc_count)
739 			continue;
740 
741 		modeset = &fb_helper->crtc_info[i].mode_set;
742 
743 		modeset->x = var->xoffset;
744 		modeset->y = var->yoffset;
745 
746 		if (modeset->num_connectors) {
747 			mutex_lock(&dev->mode_config.mutex);
748 			ret = crtc->funcs->set_config(modeset);
749 			mutex_unlock(&dev->mode_config.mutex);
750 			if (!ret) {
751 				info->var.xoffset = var->xoffset;
752 				info->var.yoffset = var->yoffset;
753 			}
754 		}
755 	}
756 	return ret;
757 }
758 EXPORT_SYMBOL(drm_fb_helper_pan_display);
759 
760 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
761 				  int preferred_bpp,
762 				  int (*fb_create)(struct drm_device *dev,
763 						   uint32_t fb_width,
764 						   uint32_t fb_height,
765 						   uint32_t surface_width,
766 						   uint32_t surface_height,
767 						   uint32_t surface_depth,
768 						   uint32_t surface_bpp,
769 						   struct drm_framebuffer **fb_ptr))
770 {
771 	struct drm_crtc *crtc;
772 	struct drm_connector *connector;
773 	unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
774 	unsigned int surface_width = 0, surface_height = 0;
775 	int new_fb = 0;
776 	int crtc_count = 0;
777 	int ret, i, conn_count = 0;
778 	struct fb_info *info;
779 	struct drm_framebuffer *fb;
780 	struct drm_mode_set *modeset = NULL;
781 	struct drm_fb_helper *fb_helper;
782 	uint32_t surface_depth = 24, surface_bpp = 32;
783 
784 	/* if driver picks 8 or 16 by default use that
785 	   for both depth/bpp */
786 	if (preferred_bpp != surface_bpp) {
787 		surface_depth = surface_bpp = preferred_bpp;
788 	}
789 	/* first up get a count of crtcs now in use and new min/maxes width/heights */
790 	list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
791 		struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
792 
793 		struct drm_fb_helper_cmdline_mode *cmdline_mode;
794 
795 		if (!fb_help_conn)
796 			continue;
797 
798 		cmdline_mode = &fb_help_conn->cmdline_mode;
799 
800 		if (cmdline_mode->bpp_specified) {
801 			switch (cmdline_mode->bpp) {
802 			case 8:
803 				surface_depth = surface_bpp = 8;
804 				break;
805 			case 15:
806 				surface_depth = 15;
807 				surface_bpp = 16;
808 				break;
809 			case 16:
810 				surface_depth = surface_bpp = 16;
811 				break;
812 			case 24:
813 				surface_depth = surface_bpp = 24;
814 				break;
815 			case 32:
816 				surface_depth = 24;
817 				surface_bpp = 32;
818 				break;
819 			}
820 			break;
821 		}
822 	}
823 
824 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
825 		if (drm_helper_crtc_in_use(crtc)) {
826 			if (crtc->desired_mode) {
827 				if (crtc->desired_mode->hdisplay < fb_width)
828 					fb_width = crtc->desired_mode->hdisplay;
829 
830 				if (crtc->desired_mode->vdisplay < fb_height)
831 					fb_height = crtc->desired_mode->vdisplay;
832 
833 				if (crtc->desired_mode->hdisplay > surface_width)
834 					surface_width = crtc->desired_mode->hdisplay;
835 
836 				if (crtc->desired_mode->vdisplay > surface_height)
837 					surface_height = crtc->desired_mode->vdisplay;
838 			}
839 			crtc_count++;
840 		}
841 	}
842 
843 	if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
844 		/* hmm everyone went away - assume VGA cable just fell out
845 		   and will come back later. */
846 		return 0;
847 	}
848 
849 	/* do we have an fb already? */
850 	if (list_empty(&dev->mode_config.fb_kernel_list)) {
851 		ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
852 				   surface_height, surface_depth, surface_bpp,
853 				   &fb);
854 		if (ret)
855 			return -EINVAL;
856 		new_fb = 1;
857 	} else {
858 		fb = list_first_entry(&dev->mode_config.fb_kernel_list,
859 				      struct drm_framebuffer, filp_head);
860 
861 		/* if someone hotplugs something bigger than we have already allocated, we are pwned.
862 		   As really we can't resize an fbdev that is in the wild currently due to fbdev
863 		   not really being designed for the lower layers moving stuff around under it.
864 		   - so in the grand style of things - punt. */
865 		if ((fb->width < surface_width) ||
866 		    (fb->height < surface_height)) {
867 			DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
868 			return -EINVAL;
869 		}
870 	}
871 
872 	info = fb->fbdev;
873 	fb_helper = info->par;
874 
875 	crtc_count = 0;
876 	/* okay we need to setup new connector sets in the crtcs */
877 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
878 		modeset = &fb_helper->crtc_info[crtc_count].mode_set;
879 		modeset->fb = fb;
880 		conn_count = 0;
881 		list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
882 			if (connector->encoder)
883 				if (connector->encoder->crtc == modeset->crtc) {
884 					modeset->connectors[conn_count] = connector;
885 					conn_count++;
886 					if (conn_count > fb_helper->conn_limit)
887 						BUG();
888 				}
889 		}
890 
891 		for (i = conn_count; i < fb_helper->conn_limit; i++)
892 			modeset->connectors[i] = NULL;
893 
894 		modeset->crtc = crtc;
895 		crtc_count++;
896 
897 		modeset->num_connectors = conn_count;
898 		if (modeset->crtc->desired_mode) {
899 			if (modeset->mode)
900 				drm_mode_destroy(dev, modeset->mode);
901 			modeset->mode = drm_mode_duplicate(dev,
902 							   modeset->crtc->desired_mode);
903 		}
904 	}
905 	fb_helper->crtc_count = crtc_count;
906 	fb_helper->fb = fb;
907 
908 	if (new_fb) {
909 		info->var.pixclock = 0;
910 		ret = fb_alloc_cmap(&info->cmap, modeset->crtc->gamma_size, 0);
911 		if (ret)
912 			return ret;
913 		if (register_framebuffer(info) < 0) {
914 			fb_dealloc_cmap(&info->cmap);
915 			return -EINVAL;
916 		}
917 	} else {
918 		drm_fb_helper_set_par(info);
919 	}
920 	printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
921 	       info->fix.id);
922 
923 	/* Switch back to kernel console on panic */
924 	/* multi card linked list maybe */
925 	if (list_empty(&kernel_fb_helper_list)) {
926 		printk(KERN_INFO "registered panic notifier\n");
927 		atomic_notifier_chain_register(&panic_notifier_list,
928 					       &paniced);
929 		register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
930 	}
931 	list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
932 	return 0;
933 }
934 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
935 
936 void drm_fb_helper_free(struct drm_fb_helper *helper)
937 {
938 	list_del(&helper->kernel_fb_list);
939 	if (list_empty(&kernel_fb_helper_list)) {
940 		printk(KERN_INFO "unregistered panic notifier\n");
941 		atomic_notifier_chain_unregister(&panic_notifier_list,
942 						 &paniced);
943 		unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
944 	}
945 	drm_fb_helper_crtc_free(helper);
946 	fb_dealloc_cmap(&helper->fb->fbdev->cmap);
947 }
948 EXPORT_SYMBOL(drm_fb_helper_free);
949 
950 void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
951 			    uint32_t depth)
952 {
953 	info->fix.type = FB_TYPE_PACKED_PIXELS;
954 	info->fix.visual = depth == 8 ? FB_VISUAL_PSEUDOCOLOR :
955 		FB_VISUAL_TRUECOLOR;
956 	info->fix.type_aux = 0;
957 	info->fix.xpanstep = 1; /* doing it in hw */
958 	info->fix.ypanstep = 1; /* doing it in hw */
959 	info->fix.ywrapstep = 0;
960 	info->fix.accel = FB_ACCEL_NONE;
961 	info->fix.type_aux = 0;
962 
963 	info->fix.line_length = pitch;
964 	return;
965 }
966 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
967 
968 void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
969 			    uint32_t fb_width, uint32_t fb_height)
970 {
971 	info->pseudo_palette = fb->pseudo_palette;
972 	info->var.xres_virtual = fb->width;
973 	info->var.yres_virtual = fb->height;
974 	info->var.bits_per_pixel = fb->bits_per_pixel;
975 	info->var.xoffset = 0;
976 	info->var.yoffset = 0;
977 	info->var.activate = FB_ACTIVATE_NOW;
978 	info->var.height = -1;
979 	info->var.width = -1;
980 
981 	switch (fb->depth) {
982 	case 8:
983 		info->var.red.offset = 0;
984 		info->var.green.offset = 0;
985 		info->var.blue.offset = 0;
986 		info->var.red.length = 8; /* 8bit DAC */
987 		info->var.green.length = 8;
988 		info->var.blue.length = 8;
989 		info->var.transp.offset = 0;
990 		info->var.transp.length = 0;
991 		break;
992 	case 15:
993 		info->var.red.offset = 10;
994 		info->var.green.offset = 5;
995 		info->var.blue.offset = 0;
996 		info->var.red.length = 5;
997 		info->var.green.length = 5;
998 		info->var.blue.length = 5;
999 		info->var.transp.offset = 15;
1000 		info->var.transp.length = 1;
1001 		break;
1002 	case 16:
1003 		info->var.red.offset = 11;
1004 		info->var.green.offset = 5;
1005 		info->var.blue.offset = 0;
1006 		info->var.red.length = 5;
1007 		info->var.green.length = 6;
1008 		info->var.blue.length = 5;
1009 		info->var.transp.offset = 0;
1010 		break;
1011 	case 24:
1012 		info->var.red.offset = 16;
1013 		info->var.green.offset = 8;
1014 		info->var.blue.offset = 0;
1015 		info->var.red.length = 8;
1016 		info->var.green.length = 8;
1017 		info->var.blue.length = 8;
1018 		info->var.transp.offset = 0;
1019 		info->var.transp.length = 0;
1020 		break;
1021 	case 32:
1022 		info->var.red.offset = 16;
1023 		info->var.green.offset = 8;
1024 		info->var.blue.offset = 0;
1025 		info->var.red.length = 8;
1026 		info->var.green.length = 8;
1027 		info->var.blue.length = 8;
1028 		info->var.transp.offset = 24;
1029 		info->var.transp.length = 8;
1030 		break;
1031 	default:
1032 		break;
1033 	}
1034 
1035 	info->var.xres = fb_width;
1036 	info->var.yres = fb_height;
1037 }
1038 EXPORT_SYMBOL(drm_fb_helper_fill_var);
1039