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