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 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
31 | |
32 | #include <linux/kernel.h> |
33 | #include <linux/sysrq.h> |
34 | #include <linux/slab.h> |
35 | #include <linux/fb.h> |
36 | #include <linux/module.h> |
37 | #include <linux/device.h> |
38 | #include <linux/export.h> |
39 | #include <linux/list.h> |
40 | #include <linux/notifier.h> |
41 | #include <linux/printk.h> |
42 | #include <linux/sysrq.h> |
43 | #include <asm/bug.h> |
44 | #include <drm/drmP.h> |
45 | #include <drm/drm_crtc.h> |
46 | #include <drm/drm_fb_helper.h> |
47 | #include <drm/drm_crtc_helper.h> |
48 | |
49 | #ifdef __NetBSD__ /* XXX LIST_HEAD means something else */ |
50 | static struct list_head kernel_fb_helper_list = |
51 | LIST_HEAD_INIT(kernel_fb_helper_list); |
52 | #else |
53 | static LIST_HEAD(kernel_fb_helper_list); |
54 | #endif |
55 | |
56 | /** |
57 | * DOC: fbdev helpers |
58 | * |
59 | * The fb helper functions are useful to provide an fbdev on top of a drm kernel |
60 | * mode setting driver. They can be used mostly independantely from the crtc |
61 | * helper functions used by many drivers to implement the kernel mode setting |
62 | * interfaces. |
63 | * |
64 | * Initialization is done as a three-step process with drm_fb_helper_init(), |
65 | * drm_fb_helper_single_add_all_connectors() and drm_fb_helper_initial_config(). |
66 | * Drivers with fancier requirements than the default beheviour can override the |
67 | * second step with their own code. Teardown is done with drm_fb_helper_fini(). |
68 | * |
69 | * At runtime drivers should restore the fbdev console by calling |
70 | * drm_fb_helper_restore_fbdev_mode() from their ->lastclose callback. They |
71 | * should also notify the fb helper code from updates to the output |
72 | * configuration by calling drm_fb_helper_hotplug_event(). For easier |
73 | * integration with the output polling code in drm_crtc_helper.c the modeset |
74 | * code proves a ->output_poll_changed callback. |
75 | * |
76 | * All other functions exported by the fb helper library can be used to |
77 | * implement the fbdev driver interface by the driver. |
78 | */ |
79 | |
80 | /** |
81 | * drm_fb_helper_single_add_all_connectors() - add all connectors to fbdev |
82 | * emulation helper |
83 | * @fb_helper: fbdev initialized with drm_fb_helper_init |
84 | * |
85 | * This functions adds all the available connectors for use with the given |
86 | * fb_helper. This is a separate step to allow drivers to freely assign |
87 | * connectors to the fbdev, e.g. if some are reserved for special purposes or |
88 | * not adequate to be used for the fbcon. |
89 | * |
90 | * Since this is part of the initial setup before the fbdev is published, no |
91 | * locking is required. |
92 | */ |
93 | int drm_fb_helper_single_add_all_connectors(struct drm_fb_helper *fb_helper) |
94 | { |
95 | struct drm_device *dev = fb_helper->dev; |
96 | struct drm_connector *connector; |
97 | int i; |
98 | |
99 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { |
100 | struct drm_fb_helper_connector *fb_helper_connector; |
101 | |
102 | fb_helper_connector = kzalloc(sizeof(struct drm_fb_helper_connector), GFP_KERNEL); |
103 | if (!fb_helper_connector) |
104 | goto fail; |
105 | |
106 | fb_helper_connector->connector = connector; |
107 | fb_helper->connector_info[fb_helper->connector_count++] = fb_helper_connector; |
108 | } |
109 | return 0; |
110 | fail: |
111 | for (i = 0; i < fb_helper->connector_count; i++) { |
112 | kfree(fb_helper->connector_info[i]); |
113 | fb_helper->connector_info[i] = NULL; |
114 | } |
115 | fb_helper->connector_count = 0; |
116 | return -ENOMEM; |
117 | } |
118 | EXPORT_SYMBOL(drm_fb_helper_single_add_all_connectors); |
119 | |
120 | static int drm_fb_helper_parse_command_line(struct drm_fb_helper *fb_helper) |
121 | { |
122 | struct drm_fb_helper_connector *fb_helper_conn; |
123 | int i; |
124 | |
125 | for (i = 0; i < fb_helper->connector_count; i++) { |
126 | struct drm_cmdline_mode *mode; |
127 | struct drm_connector *connector; |
128 | char *option = NULL; |
129 | |
130 | fb_helper_conn = fb_helper->connector_info[i]; |
131 | connector = fb_helper_conn->connector; |
132 | mode = &fb_helper_conn->cmdline_mode; |
133 | |
134 | /* do something on return - turn off connector maybe */ |
135 | #if defined(__NetBSD__) |
136 | prop_dictionary_t prop = device_properties(connector->dev->dev); |
137 | if (prop_dictionary_get_cstring(prop, drm_get_connector_name(connector), &option) == false) |
138 | continue; |
139 | #else |
140 | if (fb_get_options(drm_get_connector_name(connector), &option)) |
141 | continue; |
142 | #endif |
143 | |
144 | if (drm_mode_parse_command_line_for_connector(option, |
145 | connector, |
146 | mode)) { |
147 | if (mode->force) { |
148 | const char *s; |
149 | switch (mode->force) { |
150 | case DRM_FORCE_OFF: |
151 | s = "OFF" ; |
152 | break; |
153 | case DRM_FORCE_ON_DIGITAL: |
154 | s = "ON - dig" ; |
155 | break; |
156 | default: |
157 | case DRM_FORCE_ON: |
158 | s = "ON" ; |
159 | break; |
160 | } |
161 | |
162 | DRM_INFO("forcing %s connector %s\n" , |
163 | drm_get_connector_name(connector), s); |
164 | connector->force = mode->force; |
165 | } |
166 | |
167 | DRM_DEBUG_KMS("cmdline mode for connector %s %dx%d@%dHz%s%s%s\n" , |
168 | drm_get_connector_name(connector), |
169 | mode->xres, mode->yres, |
170 | mode->refresh_specified ? mode->refresh : 60, |
171 | mode->rb ? " reduced blanking" : "" , |
172 | mode->margins ? " with margins" : "" , |
173 | mode->interlace ? " interlaced" : "" ); |
174 | } |
175 | |
176 | } |
177 | return 0; |
178 | } |
179 | |
180 | static void drm_fb_helper_save_lut_atomic(struct drm_crtc *crtc, struct drm_fb_helper *helper) |
181 | { |
182 | uint16_t *r_base, *g_base, *b_base; |
183 | int i; |
184 | |
185 | if (helper->funcs->gamma_get == NULL) |
186 | return; |
187 | |
188 | r_base = crtc->gamma_store; |
189 | g_base = r_base + crtc->gamma_size; |
190 | b_base = g_base + crtc->gamma_size; |
191 | |
192 | for (i = 0; i < crtc->gamma_size; i++) |
193 | helper->funcs->gamma_get(crtc, &r_base[i], &g_base[i], &b_base[i], i); |
194 | } |
195 | |
196 | static void drm_fb_helper_restore_lut_atomic(struct drm_crtc *crtc) |
197 | { |
198 | uint16_t *r_base, *g_base, *b_base; |
199 | |
200 | if (crtc->funcs->gamma_set == NULL) |
201 | return; |
202 | |
203 | r_base = crtc->gamma_store; |
204 | g_base = r_base + crtc->gamma_size; |
205 | b_base = g_base + crtc->gamma_size; |
206 | |
207 | crtc->funcs->gamma_set(crtc, r_base, g_base, b_base, 0, crtc->gamma_size); |
208 | } |
209 | |
210 | /** |
211 | * drm_fb_helper_debug_enter - implementation for ->fb_debug_enter |
212 | * @info: fbdev registered by the helper |
213 | */ |
214 | #ifndef __NetBSD__ |
215 | int drm_fb_helper_debug_enter(struct fb_info *info) |
216 | { |
217 | return drm_fb_helper_debug_enter_fb(info->par); |
218 | } |
219 | #endif |
220 | |
221 | int |
222 | drm_fb_helper_debug_enter_fb(struct drm_fb_helper *helper) |
223 | { |
224 | struct drm_crtc_helper_funcs *funcs; |
225 | int i; |
226 | |
227 | if (list_empty(&kernel_fb_helper_list)) |
228 | return false; |
229 | |
230 | list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) { |
231 | for (i = 0; i < helper->crtc_count; i++) { |
232 | struct drm_mode_set *mode_set = |
233 | &helper->crtc_info[i].mode_set; |
234 | |
235 | if (!mode_set->crtc->enabled) |
236 | continue; |
237 | |
238 | funcs = mode_set->crtc->helper_private; |
239 | drm_fb_helper_save_lut_atomic(mode_set->crtc, helper); |
240 | funcs->mode_set_base_atomic(mode_set->crtc, |
241 | mode_set->fb, |
242 | mode_set->x, |
243 | mode_set->y, |
244 | ENTER_ATOMIC_MODE_SET); |
245 | } |
246 | } |
247 | |
248 | return 0; |
249 | } |
250 | EXPORT_SYMBOL(drm_fb_helper_debug_enter); |
251 | |
252 | /* Find the real fb for a given fb helper CRTC */ |
253 | static struct drm_framebuffer *drm_mode_config_fb(struct drm_crtc *crtc) |
254 | { |
255 | struct drm_device *dev = crtc->dev; |
256 | struct drm_crtc *c; |
257 | |
258 | list_for_each_entry(c, &dev->mode_config.crtc_list, head) { |
259 | if (crtc->base.id == c->base.id) |
260 | return c->primary->fb; |
261 | } |
262 | |
263 | return NULL; |
264 | } |
265 | |
266 | /** |
267 | * drm_fb_helper_debug_leave - implementation for ->fb_debug_leave |
268 | * @info: fbdev registered by the helper |
269 | */ |
270 | #ifndef __NetBSD__ |
271 | int drm_fb_helper_debug_leave(struct fb_info *info) |
272 | { |
273 | return drm_fb_helper_debug_leave_fb(info->par); |
274 | } |
275 | #endif |
276 | |
277 | int |
278 | drm_fb_helper_debug_leave_fb(struct drm_fb_helper *helper) |
279 | { |
280 | struct drm_crtc *crtc; |
281 | struct drm_crtc_helper_funcs *funcs; |
282 | struct drm_framebuffer *fb; |
283 | int i; |
284 | |
285 | for (i = 0; i < helper->crtc_count; i++) { |
286 | struct drm_mode_set *mode_set = &helper->crtc_info[i].mode_set; |
287 | crtc = mode_set->crtc; |
288 | funcs = crtc->helper_private; |
289 | fb = drm_mode_config_fb(crtc); |
290 | |
291 | if (!crtc->enabled) |
292 | continue; |
293 | |
294 | if (!fb) { |
295 | DRM_ERROR("no fb to restore??\n" ); |
296 | continue; |
297 | } |
298 | |
299 | drm_fb_helper_restore_lut_atomic(mode_set->crtc); |
300 | funcs->mode_set_base_atomic(mode_set->crtc, fb, crtc->x, |
301 | crtc->y, LEAVE_ATOMIC_MODE_SET); |
302 | } |
303 | |
304 | return 0; |
305 | } |
306 | EXPORT_SYMBOL(drm_fb_helper_debug_leave); |
307 | |
308 | /** |
309 | * drm_fb_helper_restore_fbdev_mode - restore fbdev configuration |
310 | * @fb_helper: fbcon to restore |
311 | * |
312 | * This should be called from driver's drm ->lastclose callback |
313 | * when implementing an fbcon on top of kms using this helper. This ensures that |
314 | * the user isn't greeted with a black screen when e.g. X dies. |
315 | */ |
316 | bool drm_fb_helper_restore_fbdev_mode(struct drm_fb_helper *fb_helper) |
317 | { |
318 | struct drm_device *dev = fb_helper->dev; |
319 | struct drm_plane *plane; |
320 | bool error = false; |
321 | int i; |
322 | |
323 | drm_warn_on_modeset_not_all_locked(dev); |
324 | |
325 | list_for_each_entry(plane, &dev->mode_config.plane_list, head) |
326 | if (plane->type != DRM_PLANE_TYPE_PRIMARY) |
327 | drm_plane_force_disable(plane); |
328 | |
329 | for (i = 0; i < fb_helper->crtc_count; i++) { |
330 | struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set; |
331 | struct drm_crtc *crtc = mode_set->crtc; |
332 | int ret; |
333 | |
334 | if (crtc->funcs->cursor_set) { |
335 | ret = crtc->funcs->cursor_set(crtc, NULL, 0, 0, 0); |
336 | if (ret) |
337 | error = true; |
338 | } |
339 | |
340 | ret = drm_mode_set_config_internal(mode_set); |
341 | if (ret) |
342 | error = true; |
343 | } |
344 | return error; |
345 | } |
346 | EXPORT_SYMBOL(drm_fb_helper_restore_fbdev_mode); |
347 | |
348 | /* |
349 | * restore fbcon display for all kms driver's using this helper, used for sysrq |
350 | * and panic handling. |
351 | */ |
352 | static bool drm_fb_helper_force_kernel_mode(void) |
353 | { |
354 | bool ret, error = false; |
355 | struct drm_fb_helper *helper; |
356 | |
357 | if (list_empty(&kernel_fb_helper_list)) |
358 | return false; |
359 | |
360 | list_for_each_entry(helper, &kernel_fb_helper_list, kernel_fb_list) { |
361 | if (helper->dev->switch_power_state == DRM_SWITCH_POWER_OFF) |
362 | continue; |
363 | |
364 | ret = drm_fb_helper_restore_fbdev_mode(helper); |
365 | if (ret) |
366 | error = true; |
367 | } |
368 | return error; |
369 | } |
370 | |
371 | static int drm_fb_helper_panic(struct notifier_block *n, unsigned long ununsed, |
372 | void *panic_str) |
373 | { |
374 | /* |
375 | * It's a waste of time and effort to switch back to text console |
376 | * if the kernel should reboot before panic messages can be seen. |
377 | */ |
378 | if (panic_timeout < 0) |
379 | return 0; |
380 | |
381 | pr_err("panic occurred, switching back to text console\n" ); |
382 | return drm_fb_helper_force_kernel_mode(); |
383 | } |
384 | |
385 | static struct notifier_block paniced = { |
386 | .notifier_call = drm_fb_helper_panic, |
387 | }; |
388 | |
389 | static bool drm_fb_helper_is_bound(struct drm_fb_helper *fb_helper) |
390 | { |
391 | struct drm_device *dev = fb_helper->dev; |
392 | struct drm_crtc *crtc; |
393 | int bound = 0, crtcs_bound = 0; |
394 | |
395 | /* Sometimes user space wants everything disabled, so don't steal the |
396 | * display if there's a master. */ |
397 | if (dev->primary->master) |
398 | return false; |
399 | |
400 | list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
401 | if (crtc->primary->fb) |
402 | crtcs_bound++; |
403 | if (crtc->primary->fb == fb_helper->fb) |
404 | bound++; |
405 | } |
406 | |
407 | if (bound < crtcs_bound) |
408 | return false; |
409 | |
410 | return true; |
411 | } |
412 | |
413 | #ifdef CONFIG_MAGIC_SYSRQ |
414 | static void drm_fb_helper_restore_work_fn(struct work_struct *ignored) |
415 | { |
416 | bool ret; |
417 | ret = drm_fb_helper_force_kernel_mode(); |
418 | if (ret == true) |
419 | DRM_ERROR("Failed to restore crtc configuration\n" ); |
420 | } |
421 | static DECLARE_WORK(drm_fb_helper_restore_work, drm_fb_helper_restore_work_fn); |
422 | |
423 | static void drm_fb_helper_sysrq(int dummy1) |
424 | { |
425 | schedule_work(&drm_fb_helper_restore_work); |
426 | } |
427 | |
428 | static struct sysrq_key_op sysrq_drm_fb_helper_restore_op = { |
429 | .handler = drm_fb_helper_sysrq, |
430 | .help_msg = "force-fb(V)" , |
431 | .action_msg = "Restore framebuffer console" , |
432 | }; |
433 | #else |
434 | static struct sysrq_key_op sysrq_drm_fb_helper_restore_op; |
435 | #endif |
436 | |
437 | #ifndef __NetBSD__ /* XXX fb info */ |
438 | static void drm_fb_helper_dpms(struct fb_info *info, int dpms_mode) |
439 | { |
440 | struct drm_fb_helper *fb_helper = info->par; |
441 | struct drm_device *dev = fb_helper->dev; |
442 | struct drm_crtc *crtc; |
443 | struct drm_connector *connector; |
444 | int i, j; |
445 | |
446 | /* |
447 | * fbdev->blank can be called from irq context in case of a panic. |
448 | * Since we already have our own special panic handler which will |
449 | * restore the fbdev console mode completely, just bail out early. |
450 | */ |
451 | if (oops_in_progress) |
452 | return; |
453 | |
454 | /* |
455 | * For each CRTC in this fb, turn the connectors on/off. |
456 | */ |
457 | drm_modeset_lock_all(dev); |
458 | if (!drm_fb_helper_is_bound(fb_helper)) { |
459 | drm_modeset_unlock_all(dev); |
460 | return; |
461 | } |
462 | |
463 | for (i = 0; i < fb_helper->crtc_count; i++) { |
464 | crtc = fb_helper->crtc_info[i].mode_set.crtc; |
465 | |
466 | if (!crtc->enabled) |
467 | continue; |
468 | |
469 | /* Walk the connectors & encoders on this fb turning them on/off */ |
470 | for (j = 0; j < fb_helper->connector_count; j++) { |
471 | connector = fb_helper->connector_info[j]->connector; |
472 | connector->funcs->dpms(connector, dpms_mode); |
473 | drm_object_property_set_value(&connector->base, |
474 | dev->mode_config.dpms_property, dpms_mode); |
475 | } |
476 | } |
477 | drm_modeset_unlock_all(dev); |
478 | } |
479 | |
480 | /** |
481 | * drm_fb_helper_blank - implementation for ->fb_blank |
482 | * @blank: desired blanking state |
483 | * @info: fbdev registered by the helper |
484 | */ |
485 | int drm_fb_helper_blank(int blank, struct fb_info *info) |
486 | { |
487 | switch (blank) { |
488 | /* Display: On; HSync: On, VSync: On */ |
489 | case FB_BLANK_UNBLANK: |
490 | drm_fb_helper_dpms(info, DRM_MODE_DPMS_ON); |
491 | break; |
492 | /* Display: Off; HSync: On, VSync: On */ |
493 | case FB_BLANK_NORMAL: |
494 | drm_fb_helper_dpms(info, DRM_MODE_DPMS_STANDBY); |
495 | break; |
496 | /* Display: Off; HSync: Off, VSync: On */ |
497 | case FB_BLANK_HSYNC_SUSPEND: |
498 | drm_fb_helper_dpms(info, DRM_MODE_DPMS_STANDBY); |
499 | break; |
500 | /* Display: Off; HSync: On, VSync: Off */ |
501 | case FB_BLANK_VSYNC_SUSPEND: |
502 | drm_fb_helper_dpms(info, DRM_MODE_DPMS_SUSPEND); |
503 | break; |
504 | /* Display: Off; HSync: Off, VSync: Off */ |
505 | case FB_BLANK_POWERDOWN: |
506 | drm_fb_helper_dpms(info, DRM_MODE_DPMS_OFF); |
507 | break; |
508 | } |
509 | return 0; |
510 | } |
511 | EXPORT_SYMBOL(drm_fb_helper_blank); |
512 | #endif |
513 | |
514 | static void drm_fb_helper_crtc_free(struct drm_fb_helper *helper) |
515 | { |
516 | int i; |
517 | |
518 | for (i = 0; i < helper->connector_count; i++) |
519 | kfree(helper->connector_info[i]); |
520 | kfree(helper->connector_info); |
521 | for (i = 0; i < helper->crtc_count; i++) { |
522 | kfree(helper->crtc_info[i].mode_set.connectors); |
523 | if (helper->crtc_info[i].mode_set.mode) |
524 | drm_mode_destroy(helper->dev, helper->crtc_info[i].mode_set.mode); |
525 | } |
526 | kfree(helper->crtc_info); |
527 | } |
528 | |
529 | /** |
530 | * drm_fb_helper_init - initialize a drm_fb_helper structure |
531 | * @dev: drm device |
532 | * @fb_helper: driver-allocated fbdev helper structure to initialize |
533 | * @crtc_count: maximum number of crtcs to support in this fbdev emulation |
534 | * @max_conn_count: max connector count |
535 | * |
536 | * This allocates the structures for the fbdev helper with the given limits. |
537 | * Note that this won't yet touch the hardware (through the driver interfaces) |
538 | * nor register the fbdev. This is only done in drm_fb_helper_initial_config() |
539 | * to allow driver writes more control over the exact init sequence. |
540 | * |
541 | * Drivers must set fb_helper->funcs before calling |
542 | * drm_fb_helper_initial_config(). |
543 | * |
544 | * RETURNS: |
545 | * Zero if everything went ok, nonzero otherwise. |
546 | */ |
547 | int drm_fb_helper_init(struct drm_device *dev, |
548 | struct drm_fb_helper *fb_helper, |
549 | int crtc_count, int max_conn_count) |
550 | { |
551 | struct drm_crtc *crtc; |
552 | int i; |
553 | |
554 | if (!max_conn_count) |
555 | return -EINVAL; |
556 | |
557 | fb_helper->dev = dev; |
558 | |
559 | INIT_LIST_HEAD(&fb_helper->kernel_fb_list); |
560 | |
561 | fb_helper->crtc_info = kcalloc(crtc_count, sizeof(struct drm_fb_helper_crtc), GFP_KERNEL); |
562 | if (!fb_helper->crtc_info) |
563 | return -ENOMEM; |
564 | |
565 | fb_helper->crtc_count = crtc_count; |
566 | fb_helper->connector_info = kcalloc(dev->mode_config.num_connector, sizeof(struct drm_fb_helper_connector *), GFP_KERNEL); |
567 | if (!fb_helper->connector_info) { |
568 | kfree(fb_helper->crtc_info); |
569 | return -ENOMEM; |
570 | } |
571 | fb_helper->connector_count = 0; |
572 | |
573 | for (i = 0; i < crtc_count; i++) { |
574 | fb_helper->crtc_info[i].mode_set.connectors = |
575 | kcalloc(max_conn_count, |
576 | sizeof(struct drm_connector *), |
577 | GFP_KERNEL); |
578 | |
579 | if (!fb_helper->crtc_info[i].mode_set.connectors) |
580 | goto out_free; |
581 | fb_helper->crtc_info[i].mode_set.num_connectors = 0; |
582 | } |
583 | |
584 | i = 0; |
585 | list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { |
586 | fb_helper->crtc_info[i].mode_set.crtc = crtc; |
587 | i++; |
588 | } |
589 | |
590 | return 0; |
591 | out_free: |
592 | drm_fb_helper_crtc_free(fb_helper); |
593 | return -ENOMEM; |
594 | } |
595 | EXPORT_SYMBOL(drm_fb_helper_init); |
596 | |
597 | void drm_fb_helper_fini(struct drm_fb_helper *fb_helper) |
598 | { |
599 | if (!list_empty(&fb_helper->kernel_fb_list)) { |
600 | list_del(&fb_helper->kernel_fb_list); |
601 | if (list_empty(&kernel_fb_helper_list)) { |
602 | pr_info("drm: unregistered panic notifier\n" ); |
603 | atomic_notifier_chain_unregister(&panic_notifier_list, |
604 | &paniced); |
605 | unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); |
606 | } |
607 | } |
608 | |
609 | drm_fb_helper_crtc_free(fb_helper); |
610 | |
611 | } |
612 | EXPORT_SYMBOL(drm_fb_helper_fini); |
613 | |
614 | #ifndef __NetBSD__ /* XXX fb info */ |
615 | static int setcolreg(struct drm_crtc *crtc, u16 red, u16 green, |
616 | u16 blue, u16 regno, struct fb_info *info) |
617 | { |
618 | struct drm_fb_helper *fb_helper = info->par; |
619 | struct drm_framebuffer *fb = fb_helper->fb; |
620 | int pindex; |
621 | |
622 | if (info->fix.visual == FB_VISUAL_TRUECOLOR) { |
623 | u32 *palette; |
624 | u32 value; |
625 | /* place color in psuedopalette */ |
626 | if (regno > 16) |
627 | return -EINVAL; |
628 | palette = (u32 *)info->pseudo_palette; |
629 | red >>= (16 - info->var.red.length); |
630 | green >>= (16 - info->var.green.length); |
631 | blue >>= (16 - info->var.blue.length); |
632 | value = (red << info->var.red.offset) | |
633 | (green << info->var.green.offset) | |
634 | (blue << info->var.blue.offset); |
635 | if (info->var.transp.length > 0) { |
636 | u32 mask = (1 << info->var.transp.length) - 1; |
637 | mask <<= info->var.transp.offset; |
638 | value |= mask; |
639 | } |
640 | palette[regno] = value; |
641 | return 0; |
642 | } |
643 | |
644 | /* |
645 | * The driver really shouldn't advertise pseudo/directcolor |
646 | * visuals if it can't deal with the palette. |
647 | */ |
648 | if (WARN_ON(!fb_helper->funcs->gamma_set || |
649 | !fb_helper->funcs->gamma_get)) |
650 | return -EINVAL; |
651 | |
652 | pindex = regno; |
653 | |
654 | if (fb->bits_per_pixel == 16) { |
655 | pindex = regno << 3; |
656 | |
657 | if (fb->depth == 16 && regno > 63) |
658 | return -EINVAL; |
659 | if (fb->depth == 15 && regno > 31) |
660 | return -EINVAL; |
661 | |
662 | if (fb->depth == 16) { |
663 | u16 r, g, b; |
664 | int i; |
665 | if (regno < 32) { |
666 | for (i = 0; i < 8; i++) |
667 | fb_helper->funcs->gamma_set(crtc, red, |
668 | green, blue, pindex + i); |
669 | } |
670 | |
671 | fb_helper->funcs->gamma_get(crtc, &r, |
672 | &g, &b, |
673 | pindex >> 1); |
674 | |
675 | for (i = 0; i < 4; i++) |
676 | fb_helper->funcs->gamma_set(crtc, r, |
677 | green, b, |
678 | (pindex >> 1) + i); |
679 | } |
680 | } |
681 | |
682 | if (fb->depth != 16) |
683 | fb_helper->funcs->gamma_set(crtc, red, green, blue, pindex); |
684 | return 0; |
685 | } |
686 | |
687 | /** |
688 | * drm_fb_helper_setcmap - implementation for ->fb_setcmap |
689 | * @cmap: cmap to set |
690 | * @info: fbdev registered by the helper |
691 | */ |
692 | int drm_fb_helper_setcmap(struct fb_cmap *cmap, struct fb_info *info) |
693 | { |
694 | struct drm_fb_helper *fb_helper = info->par; |
695 | struct drm_device *dev = fb_helper->dev; |
696 | struct drm_crtc_helper_funcs *crtc_funcs; |
697 | u16 *red, *green, *blue, *transp; |
698 | struct drm_crtc *crtc; |
699 | int i, j, rc = 0; |
700 | int start; |
701 | |
702 | drm_modeset_lock_all(dev); |
703 | if (!drm_fb_helper_is_bound(fb_helper)) { |
704 | drm_modeset_unlock_all(dev); |
705 | return -EBUSY; |
706 | } |
707 | |
708 | for (i = 0; i < fb_helper->crtc_count; i++) { |
709 | crtc = fb_helper->crtc_info[i].mode_set.crtc; |
710 | crtc_funcs = crtc->helper_private; |
711 | |
712 | red = cmap->red; |
713 | green = cmap->green; |
714 | blue = cmap->blue; |
715 | transp = cmap->transp; |
716 | start = cmap->start; |
717 | |
718 | for (j = 0; j < cmap->len; j++) { |
719 | u16 hred, hgreen, hblue, htransp = 0xffff; |
720 | |
721 | hred = *red++; |
722 | hgreen = *green++; |
723 | hblue = *blue++; |
724 | |
725 | if (transp) |
726 | htransp = *transp++; |
727 | |
728 | rc = setcolreg(crtc, hred, hgreen, hblue, start++, info); |
729 | if (rc) |
730 | goto out; |
731 | } |
732 | if (crtc_funcs->load_lut) |
733 | crtc_funcs->load_lut(crtc); |
734 | } |
735 | out: |
736 | drm_modeset_unlock_all(dev); |
737 | return rc; |
738 | } |
739 | EXPORT_SYMBOL(drm_fb_helper_setcmap); |
740 | |
741 | /** |
742 | * drm_fb_helper_check_var - implementation for ->fb_check_var |
743 | * @var: screeninfo to check |
744 | * @info: fbdev registered by the helper |
745 | */ |
746 | int drm_fb_helper_check_var(struct fb_var_screeninfo *var, |
747 | struct fb_info *info) |
748 | { |
749 | struct drm_fb_helper *fb_helper = info->par; |
750 | struct drm_framebuffer *fb = fb_helper->fb; |
751 | int depth; |
752 | |
753 | if (var->pixclock != 0 || in_dbg_master()) |
754 | return -EINVAL; |
755 | |
756 | /* Need to resize the fb object !!! */ |
757 | if (var->bits_per_pixel > fb->bits_per_pixel || |
758 | var->xres > fb->width || var->yres > fb->height || |
759 | var->xres_virtual > fb->width || var->yres_virtual > fb->height) { |
760 | DRM_DEBUG("fb userspace requested width/height/bpp is greater than current fb " |
761 | "request %dx%d-%d (virtual %dx%d) > %dx%d-%d\n" , |
762 | var->xres, var->yres, var->bits_per_pixel, |
763 | var->xres_virtual, var->yres_virtual, |
764 | fb->width, fb->height, fb->bits_per_pixel); |
765 | return -EINVAL; |
766 | } |
767 | |
768 | switch (var->bits_per_pixel) { |
769 | case 16: |
770 | depth = (var->green.length == 6) ? 16 : 15; |
771 | break; |
772 | case 32: |
773 | depth = (var->transp.length > 0) ? 32 : 24; |
774 | break; |
775 | default: |
776 | depth = var->bits_per_pixel; |
777 | break; |
778 | } |
779 | |
780 | switch (depth) { |
781 | case 8: |
782 | var->red.offset = 0; |
783 | var->green.offset = 0; |
784 | var->blue.offset = 0; |
785 | var->red.length = 8; |
786 | var->green.length = 8; |
787 | var->blue.length = 8; |
788 | var->transp.length = 0; |
789 | var->transp.offset = 0; |
790 | break; |
791 | case 15: |
792 | var->red.offset = 10; |
793 | var->green.offset = 5; |
794 | var->blue.offset = 0; |
795 | var->red.length = 5; |
796 | var->green.length = 5; |
797 | var->blue.length = 5; |
798 | var->transp.length = 1; |
799 | var->transp.offset = 15; |
800 | break; |
801 | case 16: |
802 | var->red.offset = 11; |
803 | var->green.offset = 5; |
804 | var->blue.offset = 0; |
805 | var->red.length = 5; |
806 | var->green.length = 6; |
807 | var->blue.length = 5; |
808 | var->transp.length = 0; |
809 | var->transp.offset = 0; |
810 | break; |
811 | case 24: |
812 | var->red.offset = 16; |
813 | var->green.offset = 8; |
814 | var->blue.offset = 0; |
815 | var->red.length = 8; |
816 | var->green.length = 8; |
817 | var->blue.length = 8; |
818 | var->transp.length = 0; |
819 | var->transp.offset = 0; |
820 | break; |
821 | case 32: |
822 | var->red.offset = 16; |
823 | var->green.offset = 8; |
824 | var->blue.offset = 0; |
825 | var->red.length = 8; |
826 | var->green.length = 8; |
827 | var->blue.length = 8; |
828 | var->transp.length = 8; |
829 | var->transp.offset = 24; |
830 | break; |
831 | default: |
832 | return -EINVAL; |
833 | } |
834 | return 0; |
835 | } |
836 | EXPORT_SYMBOL(drm_fb_helper_check_var); |
837 | #endif |
838 | |
839 | #ifndef __NetBSD__ /* XXX fb info */ |
840 | /** |
841 | * drm_fb_helper_set_par - implementation for ->fb_set_par |
842 | * @info: fbdev registered by the helper |
843 | * |
844 | * This will let fbcon do the mode init and is called at initialization time by |
845 | * the fbdev core when registering the driver, and later on through the hotplug |
846 | * callback. |
847 | */ |
848 | int drm_fb_helper_set_par(struct fb_info *info) |
849 | { |
850 | struct drm_fb_helper *fb_helper = info->par; |
851 | struct fb_var_screeninfo *var = &info->var; |
852 | |
853 | if (var->pixclock != 0) { |
854 | DRM_ERROR("PIXEL CLOCK SET\n" ); |
855 | return -EINVAL; |
856 | } |
857 | |
858 | return drm_fb_helper_set_config(fb_helper); |
859 | } |
860 | EXPORT_SYMBOL(drm_fb_helper_set_par); |
861 | #endif |
862 | |
863 | int |
864 | drm_fb_helper_set_config(struct drm_fb_helper *fb_helper) |
865 | { |
866 | struct drm_device *dev = fb_helper->dev; |
867 | |
868 | drm_modeset_lock_all(dev); |
869 | drm_fb_helper_restore_fbdev_mode(fb_helper); |
870 | drm_modeset_unlock_all(dev); |
871 | |
872 | if (fb_helper->delayed_hotplug) { |
873 | fb_helper->delayed_hotplug = false; |
874 | drm_fb_helper_hotplug_event(fb_helper); |
875 | } |
876 | return 0; |
877 | } |
878 | |
879 | #ifndef __NetBSD__ /* XXX fb info */ |
880 | /** |
881 | * drm_fb_helper_pan_display - implementation for ->fb_pan_display |
882 | * @var: updated screen information |
883 | * @info: fbdev registered by the helper |
884 | */ |
885 | int drm_fb_helper_pan_display(struct fb_var_screeninfo *var, |
886 | struct fb_info *info) |
887 | { |
888 | struct drm_fb_helper *fb_helper = info->par; |
889 | struct drm_device *dev = fb_helper->dev; |
890 | struct drm_mode_set *modeset; |
891 | int ret = 0; |
892 | int i; |
893 | |
894 | drm_modeset_lock_all(dev); |
895 | if (!drm_fb_helper_is_bound(fb_helper)) { |
896 | drm_modeset_unlock_all(dev); |
897 | return -EBUSY; |
898 | } |
899 | |
900 | for (i = 0; i < fb_helper->crtc_count; i++) { |
901 | modeset = &fb_helper->crtc_info[i].mode_set; |
902 | |
903 | modeset->x = var->xoffset; |
904 | modeset->y = var->yoffset; |
905 | |
906 | if (modeset->num_connectors) { |
907 | ret = drm_mode_set_config_internal(modeset); |
908 | if (!ret) { |
909 | info->var.xoffset = var->xoffset; |
910 | info->var.yoffset = var->yoffset; |
911 | } |
912 | } |
913 | } |
914 | drm_modeset_unlock_all(dev); |
915 | return ret; |
916 | } |
917 | EXPORT_SYMBOL(drm_fb_helper_pan_display); |
918 | #endif |
919 | |
920 | /* |
921 | * Allocates the backing storage and sets up the fbdev info structure through |
922 | * the ->fb_probe callback and then registers the fbdev and sets up the panic |
923 | * notifier. |
924 | */ |
925 | static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper, |
926 | int preferred_bpp) |
927 | { |
928 | int ret = 0; |
929 | int crtc_count = 0; |
930 | int i; |
931 | #ifndef __NetBSD__ /* XXX fb info */ |
932 | struct fb_info *info; |
933 | #endif |
934 | struct drm_fb_helper_surface_size sizes; |
935 | int gamma_size = 0; |
936 | |
937 | memset(&sizes, 0, sizeof(struct drm_fb_helper_surface_size)); |
938 | sizes.surface_depth = 24; |
939 | sizes.surface_bpp = 32; |
940 | sizes.fb_width = (unsigned)-1; |
941 | sizes.fb_height = (unsigned)-1; |
942 | |
943 | /* if driver picks 8 or 16 by default use that |
944 | for both depth/bpp */ |
945 | if (preferred_bpp != sizes.surface_bpp) |
946 | sizes.surface_depth = sizes.surface_bpp = preferred_bpp; |
947 | |
948 | /* first up get a count of crtcs now in use and new min/maxes width/heights */ |
949 | for (i = 0; i < fb_helper->connector_count; i++) { |
950 | struct drm_fb_helper_connector *fb_helper_conn = fb_helper->connector_info[i]; |
951 | struct drm_cmdline_mode *cmdline_mode; |
952 | |
953 | cmdline_mode = &fb_helper_conn->cmdline_mode; |
954 | |
955 | if (cmdline_mode->bpp_specified) { |
956 | switch (cmdline_mode->bpp) { |
957 | case 8: |
958 | sizes.surface_depth = sizes.surface_bpp = 8; |
959 | break; |
960 | case 15: |
961 | sizes.surface_depth = 15; |
962 | sizes.surface_bpp = 16; |
963 | break; |
964 | case 16: |
965 | sizes.surface_depth = sizes.surface_bpp = 16; |
966 | break; |
967 | case 24: |
968 | sizes.surface_depth = sizes.surface_bpp = 24; |
969 | break; |
970 | case 32: |
971 | sizes.surface_depth = 24; |
972 | sizes.surface_bpp = 32; |
973 | break; |
974 | } |
975 | break; |
976 | } |
977 | } |
978 | |
979 | crtc_count = 0; |
980 | for (i = 0; i < fb_helper->crtc_count; i++) { |
981 | struct drm_display_mode *desired_mode; |
982 | desired_mode = fb_helper->crtc_info[i].desired_mode; |
983 | |
984 | if (desired_mode) { |
985 | if (gamma_size == 0) |
986 | gamma_size = fb_helper->crtc_info[i].mode_set.crtc->gamma_size; |
987 | if (desired_mode->hdisplay < sizes.fb_width) |
988 | sizes.fb_width = desired_mode->hdisplay; |
989 | if (desired_mode->vdisplay < sizes.fb_height) |
990 | sizes.fb_height = desired_mode->vdisplay; |
991 | if (desired_mode->hdisplay > sizes.surface_width) |
992 | sizes.surface_width = desired_mode->hdisplay; |
993 | if (desired_mode->vdisplay > sizes.surface_height) |
994 | sizes.surface_height = desired_mode->vdisplay; |
995 | crtc_count++; |
996 | } |
997 | } |
998 | |
999 | if (crtc_count == 0 || sizes.fb_width == -1 || sizes.fb_height == -1) { |
1000 | /* hmm everyone went away - assume VGA cable just fell out |
1001 | and will come back later. */ |
1002 | DRM_INFO("Cannot find any crtc or sizes - going 1024x768\n" ); |
1003 | sizes.fb_width = sizes.surface_width = 1024; |
1004 | sizes.fb_height = sizes.surface_height = 768; |
1005 | } |
1006 | |
1007 | /* push down into drivers */ |
1008 | ret = (*fb_helper->funcs->fb_probe)(fb_helper, &sizes); |
1009 | if (ret < 0) |
1010 | return ret; |
1011 | |
1012 | #ifndef __NetBSD__ /* XXX fb info */ |
1013 | info = fb_helper->fbdev; |
1014 | #endif |
1015 | |
1016 | /* |
1017 | * Set the fb pointer - usually drm_setup_crtcs does this for hotplug |
1018 | * events, but at init time drm_setup_crtcs needs to be called before |
1019 | * the fb is allocated (since we need to figure out the desired size of |
1020 | * the fb before we can allocate it ...). Hence we need to fix things up |
1021 | * here again. |
1022 | */ |
1023 | for (i = 0; i < fb_helper->crtc_count; i++) |
1024 | if (fb_helper->crtc_info[i].mode_set.num_connectors) |
1025 | fb_helper->crtc_info[i].mode_set.fb = fb_helper->fb; |
1026 | |
1027 | #ifndef __NetBSD__ /* XXX fb info */ |
1028 | info->var.pixclock = 0; |
1029 | if (register_framebuffer(info) < 0) |
1030 | return -EINVAL; |
1031 | |
1032 | dev_info(fb_helper->dev->dev, "fb%d: %s frame buffer device\n" , |
1033 | info->node, info->fix.id); |
1034 | #endif |
1035 | |
1036 | /* Switch back to kernel console on panic */ |
1037 | /* multi card linked list maybe */ |
1038 | if (list_empty(&kernel_fb_helper_list)) { |
1039 | dev_info(fb_helper->dev->dev, "registered panic notifier\n" ); |
1040 | atomic_notifier_chain_register(&panic_notifier_list, |
1041 | &paniced); |
1042 | register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op); |
1043 | } |
1044 | |
1045 | list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list); |
1046 | |
1047 | return 0; |
1048 | } |
1049 | |
1050 | #ifndef __NetBSD__ /* XXX fb info */ |
1051 | /** |
1052 | * drm_fb_helper_fill_fix - initializes fixed fbdev information |
1053 | * @info: fbdev registered by the helper |
1054 | * @pitch: desired pitch |
1055 | * @depth: desired depth |
1056 | * |
1057 | * Helper to fill in the fixed fbdev information useful for a non-accelerated |
1058 | * fbdev emulations. Drivers which support acceleration methods which impose |
1059 | * additional constraints need to set up their own limits. |
1060 | * |
1061 | * Drivers should call this (or their equivalent setup code) from their |
1062 | * ->fb_probe callback. |
1063 | */ |
1064 | void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch, |
1065 | uint32_t depth) |
1066 | { |
1067 | info->fix.type = FB_TYPE_PACKED_PIXELS; |
1068 | info->fix.visual = depth == 8 ? FB_VISUAL_PSEUDOCOLOR : |
1069 | FB_VISUAL_TRUECOLOR; |
1070 | info->fix.mmio_start = 0; |
1071 | info->fix.mmio_len = 0; |
1072 | info->fix.type_aux = 0; |
1073 | info->fix.xpanstep = 1; /* doing it in hw */ |
1074 | info->fix.ypanstep = 1; /* doing it in hw */ |
1075 | info->fix.ywrapstep = 0; |
1076 | info->fix.accel = FB_ACCEL_NONE; |
1077 | info->fix.type_aux = 0; |
1078 | |
1079 | info->fix.line_length = pitch; |
1080 | return; |
1081 | } |
1082 | EXPORT_SYMBOL(drm_fb_helper_fill_fix); |
1083 | |
1084 | /** |
1085 | * drm_fb_helper_fill_var - initalizes variable fbdev information |
1086 | * @info: fbdev instance to set up |
1087 | * @fb_helper: fb helper instance to use as template |
1088 | * @fb_width: desired fb width |
1089 | * @fb_height: desired fb height |
1090 | * |
1091 | * Sets up the variable fbdev metainformation from the given fb helper instance |
1092 | * and the drm framebuffer allocated in fb_helper->fb. |
1093 | * |
1094 | * Drivers should call this (or their equivalent setup code) from their |
1095 | * ->fb_probe callback after having allocated the fbdev backing |
1096 | * storage framebuffer. |
1097 | */ |
1098 | void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper, |
1099 | uint32_t fb_width, uint32_t fb_height) |
1100 | { |
1101 | struct drm_framebuffer *fb = fb_helper->fb; |
1102 | info->pseudo_palette = fb_helper->pseudo_palette; |
1103 | info->var.xres_virtual = fb->width; |
1104 | info->var.yres_virtual = fb->height; |
1105 | info->var.bits_per_pixel = fb->bits_per_pixel; |
1106 | info->var.accel_flags = FB_ACCELF_TEXT; |
1107 | info->var.xoffset = 0; |
1108 | info->var.yoffset = 0; |
1109 | info->var.activate = FB_ACTIVATE_NOW; |
1110 | info->var.height = -1; |
1111 | info->var.width = -1; |
1112 | |
1113 | switch (fb->depth) { |
1114 | case 8: |
1115 | info->var.red.offset = 0; |
1116 | info->var.green.offset = 0; |
1117 | info->var.blue.offset = 0; |
1118 | info->var.red.length = 8; /* 8bit DAC */ |
1119 | info->var.green.length = 8; |
1120 | info->var.blue.length = 8; |
1121 | info->var.transp.offset = 0; |
1122 | info->var.transp.length = 0; |
1123 | break; |
1124 | case 15: |
1125 | info->var.red.offset = 10; |
1126 | info->var.green.offset = 5; |
1127 | info->var.blue.offset = 0; |
1128 | info->var.red.length = 5; |
1129 | info->var.green.length = 5; |
1130 | info->var.blue.length = 5; |
1131 | info->var.transp.offset = 15; |
1132 | info->var.transp.length = 1; |
1133 | break; |
1134 | case 16: |
1135 | info->var.red.offset = 11; |
1136 | info->var.green.offset = 5; |
1137 | info->var.blue.offset = 0; |
1138 | info->var.red.length = 5; |
1139 | info->var.green.length = 6; |
1140 | info->var.blue.length = 5; |
1141 | info->var.transp.offset = 0; |
1142 | break; |
1143 | case 24: |
1144 | info->var.red.offset = 16; |
1145 | info->var.green.offset = 8; |
1146 | info->var.blue.offset = 0; |
1147 | info->var.red.length = 8; |
1148 | info->var.green.length = 8; |
1149 | info->var.blue.length = 8; |
1150 | info->var.transp.offset = 0; |
1151 | info->var.transp.length = 0; |
1152 | break; |
1153 | case 32: |
1154 | info->var.red.offset = 16; |
1155 | info->var.green.offset = 8; |
1156 | info->var.blue.offset = 0; |
1157 | info->var.red.length = 8; |
1158 | info->var.green.length = 8; |
1159 | info->var.blue.length = 8; |
1160 | info->var.transp.offset = 24; |
1161 | info->var.transp.length = 8; |
1162 | break; |
1163 | default: |
1164 | break; |
1165 | } |
1166 | |
1167 | info->var.xres = fb_width; |
1168 | info->var.yres = fb_height; |
1169 | } |
1170 | EXPORT_SYMBOL(drm_fb_helper_fill_var); |
1171 | #endif |
1172 | |
1173 | static int drm_fb_helper_probe_connector_modes(struct drm_fb_helper *fb_helper, |
1174 | uint32_t maxX, |
1175 | uint32_t maxY) |
1176 | { |
1177 | struct drm_connector *connector; |
1178 | int count = 0; |
1179 | int i; |
1180 | |
1181 | for (i = 0; i < fb_helper->connector_count; i++) { |
1182 | connector = fb_helper->connector_info[i]->connector; |
1183 | count += connector->funcs->fill_modes(connector, maxX, maxY); |
1184 | } |
1185 | |
1186 | return count; |
1187 | } |
1188 | |
1189 | struct drm_display_mode *drm_has_preferred_mode(struct drm_fb_helper_connector *fb_connector, int width, int height) |
1190 | { |
1191 | struct drm_display_mode *mode; |
1192 | |
1193 | list_for_each_entry(mode, &fb_connector->connector->modes, head) { |
1194 | if (mode->hdisplay > width || |
1195 | mode->vdisplay > height) |
1196 | continue; |
1197 | if (mode->type & DRM_MODE_TYPE_PREFERRED) |
1198 | return mode; |
1199 | } |
1200 | return NULL; |
1201 | } |
1202 | EXPORT_SYMBOL(drm_has_preferred_mode); |
1203 | |
1204 | static bool drm_has_cmdline_mode(struct drm_fb_helper_connector *fb_connector) |
1205 | { |
1206 | struct drm_cmdline_mode *cmdline_mode; |
1207 | cmdline_mode = &fb_connector->cmdline_mode; |
1208 | return cmdline_mode->specified; |
1209 | } |
1210 | |
1211 | struct drm_display_mode *drm_pick_cmdline_mode(struct drm_fb_helper_connector *fb_helper_conn, |
1212 | int width, int height) |
1213 | { |
1214 | struct drm_cmdline_mode *cmdline_mode; |
1215 | struct drm_display_mode *mode = NULL; |
1216 | bool prefer_non_interlace; |
1217 | |
1218 | cmdline_mode = &fb_helper_conn->cmdline_mode; |
1219 | if (cmdline_mode->specified == false) |
1220 | return mode; |
1221 | |
1222 | /* attempt to find a matching mode in the list of modes |
1223 | * we have gotten so far, if not add a CVT mode that conforms |
1224 | */ |
1225 | if (cmdline_mode->rb || cmdline_mode->margins) |
1226 | goto create_mode; |
1227 | |
1228 | prefer_non_interlace = !cmdline_mode->interlace; |
1229 | again: |
1230 | list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) { |
1231 | /* check width/height */ |
1232 | if (mode->hdisplay != cmdline_mode->xres || |
1233 | mode->vdisplay != cmdline_mode->yres) |
1234 | continue; |
1235 | |
1236 | if (cmdline_mode->refresh_specified) { |
1237 | if (mode->vrefresh != cmdline_mode->refresh) |
1238 | continue; |
1239 | } |
1240 | |
1241 | if (cmdline_mode->interlace) { |
1242 | if (!(mode->flags & DRM_MODE_FLAG_INTERLACE)) |
1243 | continue; |
1244 | } else if (prefer_non_interlace) { |
1245 | if (mode->flags & DRM_MODE_FLAG_INTERLACE) |
1246 | continue; |
1247 | } |
1248 | return mode; |
1249 | } |
1250 | |
1251 | if (prefer_non_interlace) { |
1252 | prefer_non_interlace = false; |
1253 | goto again; |
1254 | } |
1255 | |
1256 | create_mode: |
1257 | mode = drm_mode_create_from_cmdline_mode(fb_helper_conn->connector->dev, |
1258 | cmdline_mode); |
1259 | list_add(&mode->head, &fb_helper_conn->connector->modes); |
1260 | return mode; |
1261 | } |
1262 | EXPORT_SYMBOL(drm_pick_cmdline_mode); |
1263 | |
1264 | static bool drm_connector_enabled(struct drm_connector *connector, bool strict) |
1265 | { |
1266 | bool enable; |
1267 | |
1268 | if (strict) |
1269 | enable = connector->status == connector_status_connected; |
1270 | else |
1271 | enable = connector->status != connector_status_disconnected; |
1272 | |
1273 | return enable; |
1274 | } |
1275 | |
1276 | static void drm_enable_connectors(struct drm_fb_helper *fb_helper, |
1277 | bool *enabled) |
1278 | { |
1279 | bool any_enabled = false; |
1280 | struct drm_connector *connector; |
1281 | int i = 0; |
1282 | |
1283 | for (i = 0; i < fb_helper->connector_count; i++) { |
1284 | connector = fb_helper->connector_info[i]->connector; |
1285 | enabled[i] = drm_connector_enabled(connector, true); |
1286 | DRM_DEBUG_KMS("connector %d enabled? %s\n" , connector->base.id, |
1287 | enabled[i] ? "yes" : "no" ); |
1288 | any_enabled |= enabled[i]; |
1289 | } |
1290 | |
1291 | if (any_enabled) |
1292 | return; |
1293 | |
1294 | for (i = 0; i < fb_helper->connector_count; i++) { |
1295 | connector = fb_helper->connector_info[i]->connector; |
1296 | enabled[i] = drm_connector_enabled(connector, false); |
1297 | } |
1298 | } |
1299 | |
1300 | static bool drm_target_cloned(struct drm_fb_helper *fb_helper, |
1301 | struct drm_display_mode **modes, |
1302 | bool *enabled, int width, int height) |
1303 | { |
1304 | int count, i, j; |
1305 | bool can_clone = false; |
1306 | struct drm_fb_helper_connector *fb_helper_conn; |
1307 | struct drm_display_mode *dmt_mode, *mode; |
1308 | |
1309 | /* only contemplate cloning in the single crtc case */ |
1310 | if (fb_helper->crtc_count > 1) |
1311 | return false; |
1312 | |
1313 | count = 0; |
1314 | for (i = 0; i < fb_helper->connector_count; i++) { |
1315 | if (enabled[i]) |
1316 | count++; |
1317 | } |
1318 | |
1319 | /* only contemplate cloning if more than one connector is enabled */ |
1320 | if (count <= 1) |
1321 | return false; |
1322 | |
1323 | /* check the command line or if nothing common pick 1024x768 */ |
1324 | can_clone = true; |
1325 | for (i = 0; i < fb_helper->connector_count; i++) { |
1326 | if (!enabled[i]) |
1327 | continue; |
1328 | fb_helper_conn = fb_helper->connector_info[i]; |
1329 | modes[i] = drm_pick_cmdline_mode(fb_helper_conn, width, height); |
1330 | if (!modes[i]) { |
1331 | can_clone = false; |
1332 | break; |
1333 | } |
1334 | for (j = 0; j < i; j++) { |
1335 | if (!enabled[j]) |
1336 | continue; |
1337 | if (!drm_mode_equal(modes[j], modes[i])) |
1338 | can_clone = false; |
1339 | } |
1340 | } |
1341 | |
1342 | if (can_clone) { |
1343 | DRM_DEBUG_KMS("can clone using command line\n" ); |
1344 | return true; |
1345 | } |
1346 | |
1347 | /* try and find a 1024x768 mode on each connector */ |
1348 | can_clone = true; |
1349 | dmt_mode = drm_mode_find_dmt(fb_helper->dev, 1024, 768, 60, false); |
1350 | |
1351 | for (i = 0; i < fb_helper->connector_count; i++) { |
1352 | |
1353 | if (!enabled[i]) |
1354 | continue; |
1355 | |
1356 | fb_helper_conn = fb_helper->connector_info[i]; |
1357 | list_for_each_entry(mode, &fb_helper_conn->connector->modes, head) { |
1358 | if (drm_mode_equal(mode, dmt_mode)) |
1359 | modes[i] = mode; |
1360 | } |
1361 | if (!modes[i]) |
1362 | can_clone = false; |
1363 | } |
1364 | |
1365 | if (can_clone) { |
1366 | DRM_DEBUG_KMS("can clone using 1024x768\n" ); |
1367 | return true; |
1368 | } |
1369 | DRM_INFO("kms: can't enable cloning when we probably wanted to.\n" ); |
1370 | return false; |
1371 | } |
1372 | |
1373 | static bool drm_target_preferred(struct drm_fb_helper *fb_helper, |
1374 | struct drm_display_mode **modes, |
1375 | bool *enabled, int width, int height) |
1376 | { |
1377 | struct drm_fb_helper_connector *fb_helper_conn; |
1378 | int i; |
1379 | |
1380 | for (i = 0; i < fb_helper->connector_count; i++) { |
1381 | fb_helper_conn = fb_helper->connector_info[i]; |
1382 | |
1383 | if (enabled[i] == false) |
1384 | continue; |
1385 | |
1386 | DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n" , |
1387 | fb_helper_conn->connector->base.id); |
1388 | |
1389 | /* got for command line mode first */ |
1390 | modes[i] = drm_pick_cmdline_mode(fb_helper_conn, width, height); |
1391 | if (!modes[i]) { |
1392 | DRM_DEBUG_KMS("looking for preferred mode on connector %d\n" , |
1393 | fb_helper_conn->connector->base.id); |
1394 | modes[i] = drm_has_preferred_mode(fb_helper_conn, width, height); |
1395 | } |
1396 | /* No preferred modes, pick one off the list */ |
1397 | if (!modes[i] && !list_empty(&fb_helper_conn->connector->modes)) { |
1398 | list_for_each_entry(modes[i], &fb_helper_conn->connector->modes, head) |
1399 | break; |
1400 | } |
1401 | DRM_DEBUG_KMS("found mode %s\n" , modes[i] ? modes[i]->name : |
1402 | "none" ); |
1403 | } |
1404 | return true; |
1405 | } |
1406 | |
1407 | static int drm_pick_crtcs(struct drm_fb_helper *fb_helper, |
1408 | struct drm_fb_helper_crtc **best_crtcs, |
1409 | struct drm_display_mode **modes, |
1410 | int n, int width, int height) |
1411 | { |
1412 | int c, o; |
1413 | struct drm_device *dev = fb_helper->dev; |
1414 | struct drm_connector *connector; |
1415 | struct drm_connector_helper_funcs *connector_funcs; |
1416 | struct drm_encoder *encoder; |
1417 | int my_score, best_score, score; |
1418 | struct drm_fb_helper_crtc **crtcs, *crtc; |
1419 | struct drm_fb_helper_connector *fb_helper_conn; |
1420 | |
1421 | if (n == fb_helper->connector_count) |
1422 | return 0; |
1423 | |
1424 | fb_helper_conn = fb_helper->connector_info[n]; |
1425 | connector = fb_helper_conn->connector; |
1426 | |
1427 | best_crtcs[n] = NULL; |
1428 | best_score = drm_pick_crtcs(fb_helper, best_crtcs, modes, n+1, width, height); |
1429 | if (modes[n] == NULL) |
1430 | return best_score; |
1431 | |
1432 | crtcs = kzalloc(dev->mode_config.num_connector * |
1433 | sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); |
1434 | if (!crtcs) |
1435 | return best_score; |
1436 | |
1437 | my_score = 1; |
1438 | if (connector->status == connector_status_connected) |
1439 | my_score++; |
1440 | if (drm_has_cmdline_mode(fb_helper_conn)) |
1441 | my_score++; |
1442 | if (drm_has_preferred_mode(fb_helper_conn, width, height)) |
1443 | my_score++; |
1444 | |
1445 | connector_funcs = connector->helper_private; |
1446 | encoder = connector_funcs->best_encoder(connector); |
1447 | if (!encoder) |
1448 | goto out; |
1449 | |
1450 | /* select a crtc for this connector and then attempt to configure |
1451 | remaining connectors */ |
1452 | for (c = 0; c < fb_helper->crtc_count; c++) { |
1453 | crtc = &fb_helper->crtc_info[c]; |
1454 | |
1455 | if ((encoder->possible_crtcs & (1 << c)) == 0) |
1456 | continue; |
1457 | |
1458 | for (o = 0; o < n; o++) |
1459 | if (best_crtcs[o] == crtc) |
1460 | break; |
1461 | |
1462 | if (o < n) { |
1463 | /* ignore cloning unless only a single crtc */ |
1464 | if (fb_helper->crtc_count > 1) |
1465 | continue; |
1466 | |
1467 | if (!drm_mode_equal(modes[o], modes[n])) |
1468 | continue; |
1469 | } |
1470 | |
1471 | crtcs[n] = crtc; |
1472 | memcpy(crtcs, best_crtcs, n * sizeof(struct drm_fb_helper_crtc *)); |
1473 | score = my_score + drm_pick_crtcs(fb_helper, crtcs, modes, n + 1, |
1474 | width, height); |
1475 | if (score > best_score) { |
1476 | best_score = score; |
1477 | memcpy(best_crtcs, crtcs, |
1478 | dev->mode_config.num_connector * |
1479 | sizeof(struct drm_fb_helper_crtc *)); |
1480 | } |
1481 | } |
1482 | out: |
1483 | kfree(crtcs); |
1484 | return best_score; |
1485 | } |
1486 | |
1487 | static void drm_setup_crtcs(struct drm_fb_helper *fb_helper) |
1488 | { |
1489 | struct drm_device *dev = fb_helper->dev; |
1490 | struct drm_fb_helper_crtc **crtcs; |
1491 | struct drm_display_mode **modes; |
1492 | struct drm_mode_set *modeset; |
1493 | bool *enabled; |
1494 | int width, height; |
1495 | int i; |
1496 | |
1497 | DRM_DEBUG_KMS("\n" ); |
1498 | |
1499 | width = dev->mode_config.max_width; |
1500 | height = dev->mode_config.max_height; |
1501 | |
1502 | crtcs = kcalloc(dev->mode_config.num_connector, |
1503 | sizeof(struct drm_fb_helper_crtc *), GFP_KERNEL); |
1504 | modes = kcalloc(dev->mode_config.num_connector, |
1505 | sizeof(struct drm_display_mode *), GFP_KERNEL); |
1506 | enabled = kcalloc(dev->mode_config.num_connector, |
1507 | sizeof(bool), GFP_KERNEL); |
1508 | if (!crtcs || !modes || !enabled) { |
1509 | DRM_ERROR("Memory allocation failed\n" ); |
1510 | goto out; |
1511 | } |
1512 | |
1513 | |
1514 | drm_enable_connectors(fb_helper, enabled); |
1515 | |
1516 | if (!(fb_helper->funcs->initial_config && |
1517 | fb_helper->funcs->initial_config(fb_helper, crtcs, modes, |
1518 | enabled, width, height))) { |
1519 | memset(modes, 0, dev->mode_config.num_connector*sizeof(modes[0])); |
1520 | memset(crtcs, 0, dev->mode_config.num_connector*sizeof(crtcs[0])); |
1521 | |
1522 | if (!drm_target_cloned(fb_helper, |
1523 | modes, enabled, width, height) && |
1524 | !drm_target_preferred(fb_helper, |
1525 | modes, enabled, width, height)) |
1526 | DRM_ERROR("Unable to find initial modes\n" ); |
1527 | |
1528 | DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n" , |
1529 | width, height); |
1530 | |
1531 | drm_pick_crtcs(fb_helper, crtcs, modes, 0, width, height); |
1532 | } |
1533 | |
1534 | /* need to set the modesets up here for use later */ |
1535 | /* fill out the connector<->crtc mappings into the modesets */ |
1536 | for (i = 0; i < fb_helper->crtc_count; i++) { |
1537 | modeset = &fb_helper->crtc_info[i].mode_set; |
1538 | modeset->num_connectors = 0; |
1539 | modeset->fb = NULL; |
1540 | } |
1541 | |
1542 | for (i = 0; i < fb_helper->connector_count; i++) { |
1543 | struct drm_display_mode *mode = modes[i]; |
1544 | struct drm_fb_helper_crtc *fb_crtc = crtcs[i]; |
1545 | modeset = &fb_crtc->mode_set; |
1546 | |
1547 | if (mode && fb_crtc) { |
1548 | DRM_DEBUG_KMS("desired mode %s set on crtc %d\n" , |
1549 | mode->name, fb_crtc->mode_set.crtc->base.id); |
1550 | fb_crtc->desired_mode = mode; |
1551 | if (modeset->mode) |
1552 | drm_mode_destroy(dev, modeset->mode); |
1553 | modeset->mode = drm_mode_duplicate(dev, |
1554 | fb_crtc->desired_mode); |
1555 | modeset->connectors[modeset->num_connectors++] = fb_helper->connector_info[i]->connector; |
1556 | modeset->fb = fb_helper->fb; |
1557 | } |
1558 | } |
1559 | |
1560 | /* Clear out any old modes if there are no more connected outputs. */ |
1561 | for (i = 0; i < fb_helper->crtc_count; i++) { |
1562 | modeset = &fb_helper->crtc_info[i].mode_set; |
1563 | if (modeset->num_connectors == 0) { |
1564 | BUG_ON(modeset->fb); |
1565 | BUG_ON(modeset->num_connectors); |
1566 | if (modeset->mode) |
1567 | drm_mode_destroy(dev, modeset->mode); |
1568 | modeset->mode = NULL; |
1569 | fb_helper->crtc_info[i].desired_mode = NULL; |
1570 | } |
1571 | } |
1572 | out: |
1573 | kfree(crtcs); |
1574 | kfree(modes); |
1575 | kfree(enabled); |
1576 | } |
1577 | |
1578 | /** |
1579 | * drm_fb_helper_initial_config - setup a sane initial connector configuration |
1580 | * @fb_helper: fb_helper device struct |
1581 | * @bpp_sel: bpp value to use for the framebuffer configuration |
1582 | * |
1583 | * Scans the CRTCs and connectors and tries to put together an initial setup. |
1584 | * At the moment, this is a cloned configuration across all heads with |
1585 | * a new framebuffer object as the backing store. |
1586 | * |
1587 | * Note that this also registers the fbdev and so allows userspace to call into |
1588 | * the driver through the fbdev interfaces. |
1589 | * |
1590 | * This function will call down into the ->fb_probe callback to let |
1591 | * the driver allocate and initialize the fbdev info structure and the drm |
1592 | * framebuffer used to back the fbdev. drm_fb_helper_fill_var() and |
1593 | * drm_fb_helper_fill_fix() are provided as helpers to setup simple default |
1594 | * values for the fbdev info structure. |
1595 | * |
1596 | * RETURNS: |
1597 | * Zero if everything went ok, nonzero otherwise. |
1598 | */ |
1599 | bool drm_fb_helper_initial_config(struct drm_fb_helper *fb_helper, int bpp_sel) |
1600 | { |
1601 | struct drm_device *dev = fb_helper->dev; |
1602 | int count = 0; |
1603 | |
1604 | drm_fb_helper_parse_command_line(fb_helper); |
1605 | |
1606 | mutex_lock(&dev->mode_config.mutex); |
1607 | count = drm_fb_helper_probe_connector_modes(fb_helper, |
1608 | dev->mode_config.max_width, |
1609 | dev->mode_config.max_height); |
1610 | mutex_unlock(&dev->mode_config.mutex); |
1611 | /* |
1612 | * we shouldn't end up with no modes here. |
1613 | */ |
1614 | if (count == 0) |
1615 | dev_info(fb_helper->dev->dev, "No connectors reported connected with modes\n" ); |
1616 | |
1617 | drm_setup_crtcs(fb_helper); |
1618 | |
1619 | return drm_fb_helper_single_fb_probe(fb_helper, bpp_sel); |
1620 | } |
1621 | EXPORT_SYMBOL(drm_fb_helper_initial_config); |
1622 | |
1623 | /** |
1624 | * drm_fb_helper_hotplug_event - respond to a hotplug notification by |
1625 | * probing all the outputs attached to the fb |
1626 | * @fb_helper: the drm_fb_helper |
1627 | * |
1628 | * Scan the connectors attached to the fb_helper and try to put together a |
1629 | * setup after *notification of a change in output configuration. |
1630 | * |
1631 | * Called at runtime, takes the mode config locks to be able to check/change the |
1632 | * modeset configuration. Must be run from process context (which usually means |
1633 | * either the output polling work or a work item launched from the driver's |
1634 | * hotplug interrupt). |
1635 | * |
1636 | * Note that the driver must ensure that this is only called _after_ the fb has |
1637 | * been fully set up, i.e. after the call to drm_fb_helper_initial_config. |
1638 | * |
1639 | * RETURNS: |
1640 | * 0 on success and a non-zero error code otherwise. |
1641 | */ |
1642 | int drm_fb_helper_hotplug_event(struct drm_fb_helper *fb_helper) |
1643 | { |
1644 | struct drm_device *dev = fb_helper->dev; |
1645 | u32 max_width, max_height; |
1646 | |
1647 | if (!fb_helper->fb) |
1648 | return 0; |
1649 | |
1650 | mutex_lock(&fb_helper->dev->mode_config.mutex); |
1651 | if (!drm_fb_helper_is_bound(fb_helper)) { |
1652 | fb_helper->delayed_hotplug = true; |
1653 | mutex_unlock(&fb_helper->dev->mode_config.mutex); |
1654 | return 0; |
1655 | } |
1656 | DRM_DEBUG_KMS("\n" ); |
1657 | |
1658 | max_width = fb_helper->fb->width; |
1659 | max_height = fb_helper->fb->height; |
1660 | |
1661 | drm_fb_helper_probe_connector_modes(fb_helper, max_width, max_height); |
1662 | mutex_unlock(&fb_helper->dev->mode_config.mutex); |
1663 | |
1664 | drm_modeset_lock_all(dev); |
1665 | drm_setup_crtcs(fb_helper); |
1666 | drm_modeset_unlock_all(dev); |
1667 | #ifdef __NetBSD__ |
1668 | drm_fb_helper_set_config(fb_helper); |
1669 | #else |
1670 | drm_fb_helper_set_par(fb_helper->fbdev); |
1671 | #endif |
1672 | |
1673 | return 0; |
1674 | } |
1675 | EXPORT_SYMBOL(drm_fb_helper_hotplug_event); |
1676 | |
1677 | /* The Kconfig DRM_KMS_HELPER selects FRAMEBUFFER_CONSOLE (if !EXPERT) |
1678 | * but the module doesn't depend on any fb console symbols. At least |
1679 | * attempt to load fbcon to avoid leaving the system without a usable console. |
1680 | */ |
1681 | #if defined(CONFIG_FRAMEBUFFER_CONSOLE_MODULE) && !defined(CONFIG_EXPERT) |
1682 | static int __init drm_fb_helper_modinit(void) |
1683 | { |
1684 | const char *name = "fbcon" ; |
1685 | struct module *fbcon; |
1686 | |
1687 | mutex_lock(&module_mutex); |
1688 | fbcon = find_module(name); |
1689 | mutex_unlock(&module_mutex); |
1690 | |
1691 | if (!fbcon) |
1692 | request_module_nowait(name); |
1693 | return 0; |
1694 | } |
1695 | |
1696 | module_init(drm_fb_helper_modinit); |
1697 | #endif |
1698 | |