2 * Copyright © 2007 David Airlie
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice (including the next
12 * paragraph) shall be included in all copies or substantial portions of the
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21 * DEALINGS IN THE SOFTWARE.
27 #include <linux/module.h>
28 #include <linux/kernel.h>
29 #include <linux/errno.h>
30 #include <linux/string.h>
32 #include <linux/tty.h>
33 #include <linux/sysrq.h>
34 #include <linux/delay.h>
35 #include <linux/init.h>
36 #include <linux/screen_info.h>
37 #include <linux/vga_switcheroo.h>
38 #include <linux/console.h>
41 #include <drm/drm_crtc.h>
42 #include <drm/drm_crtc_helper.h>
43 #include <drm/drm_fb_helper.h>
44 #include <drm/drm_atomic.h>
46 #include "nouveau_drv.h"
47 #include "nouveau_gem.h"
48 #include "nouveau_bo.h"
49 #include "nouveau_fbcon.h"
50 #include "nouveau_chan.h"
51 #include "nouveau_vmm.h"
53 #include "nouveau_crtc.h"
55 MODULE_PARM_DESC(nofbaccel
, "Disable fbcon acceleration");
56 int nouveau_nofbaccel
= 0;
57 module_param_named(nofbaccel
, nouveau_nofbaccel
, int, 0400);
59 MODULE_PARM_DESC(fbcon_bpp
, "fbcon bits-per-pixel (default: auto)");
60 static int nouveau_fbcon_bpp
;
61 module_param_named(fbcon_bpp
, nouveau_fbcon_bpp
, int, 0400);
64 nouveau_fbcon_fillrect(struct fb_info
*info
, const struct fb_fillrect
*rect
)
66 struct nouveau_fbdev
*fbcon
= info
->par
;
67 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
68 struct nvif_device
*device
= &drm
->client
.device
;
71 if (info
->state
!= FBINFO_STATE_RUNNING
)
75 if (!in_interrupt() && !(info
->flags
& FBINFO_HWACCEL_DISABLED
) &&
76 mutex_trylock(&drm
->client
.mutex
)) {
77 if (device
->info
.family
< NV_DEVICE_INFO_V0_TESLA
)
78 ret
= nv04_fbcon_fillrect(info
, rect
);
80 if (device
->info
.family
< NV_DEVICE_INFO_V0_FERMI
)
81 ret
= nv50_fbcon_fillrect(info
, rect
);
83 ret
= nvc0_fbcon_fillrect(info
, rect
);
84 mutex_unlock(&drm
->client
.mutex
);
91 nouveau_fbcon_gpu_lockup(info
);
92 drm_fb_helper_cfb_fillrect(info
, rect
);
96 nouveau_fbcon_copyarea(struct fb_info
*info
, const struct fb_copyarea
*image
)
98 struct nouveau_fbdev
*fbcon
= info
->par
;
99 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
100 struct nvif_device
*device
= &drm
->client
.device
;
103 if (info
->state
!= FBINFO_STATE_RUNNING
)
107 if (!in_interrupt() && !(info
->flags
& FBINFO_HWACCEL_DISABLED
) &&
108 mutex_trylock(&drm
->client
.mutex
)) {
109 if (device
->info
.family
< NV_DEVICE_INFO_V0_TESLA
)
110 ret
= nv04_fbcon_copyarea(info
, image
);
112 if (device
->info
.family
< NV_DEVICE_INFO_V0_FERMI
)
113 ret
= nv50_fbcon_copyarea(info
, image
);
115 ret
= nvc0_fbcon_copyarea(info
, image
);
116 mutex_unlock(&drm
->client
.mutex
);
123 nouveau_fbcon_gpu_lockup(info
);
124 drm_fb_helper_cfb_copyarea(info
, image
);
128 nouveau_fbcon_imageblit(struct fb_info
*info
, const struct fb_image
*image
)
130 struct nouveau_fbdev
*fbcon
= info
->par
;
131 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
132 struct nvif_device
*device
= &drm
->client
.device
;
135 if (info
->state
!= FBINFO_STATE_RUNNING
)
139 if (!in_interrupt() && !(info
->flags
& FBINFO_HWACCEL_DISABLED
) &&
140 mutex_trylock(&drm
->client
.mutex
)) {
141 if (device
->info
.family
< NV_DEVICE_INFO_V0_TESLA
)
142 ret
= nv04_fbcon_imageblit(info
, image
);
144 if (device
->info
.family
< NV_DEVICE_INFO_V0_FERMI
)
145 ret
= nv50_fbcon_imageblit(info
, image
);
147 ret
= nvc0_fbcon_imageblit(info
, image
);
148 mutex_unlock(&drm
->client
.mutex
);
155 nouveau_fbcon_gpu_lockup(info
);
156 drm_fb_helper_cfb_imageblit(info
, image
);
160 nouveau_fbcon_sync(struct fb_info
*info
)
162 struct nouveau_fbdev
*fbcon
= info
->par
;
163 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
164 struct nouveau_channel
*chan
= drm
->channel
;
167 if (!chan
|| !chan
->accel_done
|| in_interrupt() ||
168 info
->state
!= FBINFO_STATE_RUNNING
||
169 info
->flags
& FBINFO_HWACCEL_DISABLED
)
172 if (!mutex_trylock(&drm
->client
.mutex
))
175 ret
= nouveau_channel_idle(chan
);
176 mutex_unlock(&drm
->client
.mutex
);
178 nouveau_fbcon_gpu_lockup(info
);
182 chan
->accel_done
= false;
187 nouveau_fbcon_open(struct fb_info
*info
, int user
)
189 struct nouveau_fbdev
*fbcon
= info
->par
;
190 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
191 int ret
= pm_runtime_get_sync(drm
->dev
->dev
);
192 if (ret
< 0 && ret
!= -EACCES
)
198 nouveau_fbcon_release(struct fb_info
*info
, int user
)
200 struct nouveau_fbdev
*fbcon
= info
->par
;
201 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
202 pm_runtime_put(drm
->dev
->dev
);
206 static struct fb_ops nouveau_fbcon_ops
= {
207 .owner
= THIS_MODULE
,
208 DRM_FB_HELPER_DEFAULT_OPS
,
209 .fb_open
= nouveau_fbcon_open
,
210 .fb_release
= nouveau_fbcon_release
,
211 .fb_fillrect
= nouveau_fbcon_fillrect
,
212 .fb_copyarea
= nouveau_fbcon_copyarea
,
213 .fb_imageblit
= nouveau_fbcon_imageblit
,
214 .fb_sync
= nouveau_fbcon_sync
,
217 static struct fb_ops nouveau_fbcon_sw_ops
= {
218 .owner
= THIS_MODULE
,
219 DRM_FB_HELPER_DEFAULT_OPS
,
220 .fb_open
= nouveau_fbcon_open
,
221 .fb_release
= nouveau_fbcon_release
,
222 .fb_fillrect
= drm_fb_helper_cfb_fillrect
,
223 .fb_copyarea
= drm_fb_helper_cfb_copyarea
,
224 .fb_imageblit
= drm_fb_helper_cfb_imageblit
,
228 nouveau_fbcon_accel_save_disable(struct drm_device
*dev
)
230 struct nouveau_drm
*drm
= nouveau_drm(dev
);
231 if (drm
->fbcon
&& drm
->fbcon
->helper
.fbdev
) {
232 drm
->fbcon
->saved_flags
= drm
->fbcon
->helper
.fbdev
->flags
;
233 drm
->fbcon
->helper
.fbdev
->flags
|= FBINFO_HWACCEL_DISABLED
;
238 nouveau_fbcon_accel_restore(struct drm_device
*dev
)
240 struct nouveau_drm
*drm
= nouveau_drm(dev
);
241 if (drm
->fbcon
&& drm
->fbcon
->helper
.fbdev
) {
242 drm
->fbcon
->helper
.fbdev
->flags
= drm
->fbcon
->saved_flags
;
247 nouveau_fbcon_accel_fini(struct drm_device
*dev
)
249 struct nouveau_drm
*drm
= nouveau_drm(dev
);
250 struct nouveau_fbdev
*fbcon
= drm
->fbcon
;
251 if (fbcon
&& drm
->channel
) {
253 if (fbcon
->helper
.fbdev
)
254 fbcon
->helper
.fbdev
->flags
|= FBINFO_HWACCEL_DISABLED
;
256 nouveau_channel_idle(drm
->channel
);
257 nvif_object_fini(&fbcon
->twod
);
258 nvif_object_fini(&fbcon
->blit
);
259 nvif_object_fini(&fbcon
->gdi
);
260 nvif_object_fini(&fbcon
->patt
);
261 nvif_object_fini(&fbcon
->rop
);
262 nvif_object_fini(&fbcon
->clip
);
263 nvif_object_fini(&fbcon
->surf2d
);
268 nouveau_fbcon_accel_init(struct drm_device
*dev
)
270 struct nouveau_drm
*drm
= nouveau_drm(dev
);
271 struct nouveau_fbdev
*fbcon
= drm
->fbcon
;
272 struct fb_info
*info
= fbcon
->helper
.fbdev
;
275 if (drm
->client
.device
.info
.family
< NV_DEVICE_INFO_V0_TESLA
)
276 ret
= nv04_fbcon_accel_init(info
);
278 if (drm
->client
.device
.info
.family
< NV_DEVICE_INFO_V0_FERMI
)
279 ret
= nv50_fbcon_accel_init(info
);
281 ret
= nvc0_fbcon_accel_init(info
);
284 info
->fbops
= &nouveau_fbcon_ops
;
288 nouveau_fbcon_zfill(struct drm_device
*dev
, struct nouveau_fbdev
*fbcon
)
290 struct fb_info
*info
= fbcon
->helper
.fbdev
;
291 struct fb_fillrect rect
;
293 /* Clear the entire fbcon. The drm will program every connector
294 * with it's preferred mode. If the sizes differ, one display will
295 * quite likely have garbage around the console.
297 rect
.dx
= rect
.dy
= 0;
298 rect
.width
= info
->var
.xres_virtual
;
299 rect
.height
= info
->var
.yres_virtual
;
302 info
->fbops
->fb_fillrect(info
, &rect
);
306 nouveau_fbcon_create(struct drm_fb_helper
*helper
,
307 struct drm_fb_helper_surface_size
*sizes
)
309 struct nouveau_fbdev
*fbcon
=
310 container_of(helper
, struct nouveau_fbdev
, helper
);
311 struct drm_device
*dev
= fbcon
->helper
.dev
;
312 struct nouveau_drm
*drm
= nouveau_drm(dev
);
313 struct nvif_device
*device
= &drm
->client
.device
;
314 struct fb_info
*info
;
315 struct nouveau_framebuffer
*fb
;
316 struct nouveau_channel
*chan
;
317 struct nouveau_bo
*nvbo
;
318 struct drm_mode_fb_cmd2 mode_cmd
;
321 mode_cmd
.width
= sizes
->surface_width
;
322 mode_cmd
.height
= sizes
->surface_height
;
324 mode_cmd
.pitches
[0] = mode_cmd
.width
* (sizes
->surface_bpp
>> 3);
325 mode_cmd
.pitches
[0] = roundup(mode_cmd
.pitches
[0], 256);
327 mode_cmd
.pixel_format
= drm_mode_legacy_fb_format(sizes
->surface_bpp
,
328 sizes
->surface_depth
);
330 ret
= nouveau_gem_new(&drm
->client
, mode_cmd
.pitches
[0] *
331 mode_cmd
.height
, 0, NOUVEAU_GEM_DOMAIN_VRAM
,
334 NV_ERROR(drm
, "failed to allocate framebuffer\n");
338 ret
= nouveau_framebuffer_new(dev
, &mode_cmd
, nvbo
, &fb
);
342 ret
= nouveau_bo_pin(nvbo
, TTM_PL_FLAG_VRAM
, false);
344 NV_ERROR(drm
, "failed to pin fb: %d\n", ret
);
348 ret
= nouveau_bo_map(nvbo
);
350 NV_ERROR(drm
, "failed to map fb: %d\n", ret
);
354 chan
= nouveau_nofbaccel
? NULL
: drm
->channel
;
355 if (chan
&& device
->info
.family
>= NV_DEVICE_INFO_V0_TESLA
) {
356 ret
= nouveau_vma_new(nvbo
, &drm
->client
.vmm
, &fb
->vma
);
358 NV_ERROR(drm
, "failed to map fb into chan: %d\n", ret
);
363 info
= drm_fb_helper_alloc_fbi(helper
);
368 info
->skip_vt_switch
= 1;
373 fbcon
->helper
.fb
= &fb
->base
;
375 strcpy(info
->fix
.id
, "nouveaufb");
377 info
->flags
= FBINFO_DEFAULT
| FBINFO_HWACCEL_DISABLED
;
379 info
->flags
= FBINFO_DEFAULT
| FBINFO_HWACCEL_COPYAREA
|
380 FBINFO_HWACCEL_FILLRECT
|
381 FBINFO_HWACCEL_IMAGEBLIT
;
382 info
->flags
|= FBINFO_CAN_FORCE_OUTPUT
;
383 info
->fbops
= &nouveau_fbcon_sw_ops
;
384 info
->fix
.smem_start
= fb
->nvbo
->bo
.mem
.bus
.base
+
385 fb
->nvbo
->bo
.mem
.bus
.offset
;
386 info
->fix
.smem_len
= fb
->nvbo
->bo
.mem
.num_pages
<< PAGE_SHIFT
;
388 info
->screen_base
= nvbo_kmap_obj_iovirtual(fb
->nvbo
);
389 info
->screen_size
= fb
->nvbo
->bo
.mem
.num_pages
<< PAGE_SHIFT
;
391 drm_fb_helper_fill_fix(info
, fb
->base
.pitches
[0],
392 fb
->base
.format
->depth
);
393 drm_fb_helper_fill_var(info
, &fbcon
->helper
, sizes
->fb_width
, sizes
->fb_height
);
395 /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
398 nouveau_fbcon_accel_init(dev
);
399 nouveau_fbcon_zfill(dev
, fbcon
);
401 /* To allow resizeing without swapping buffers */
402 NV_INFO(drm
, "allocated %dx%d fb: 0x%llx, bo %p\n",
403 fb
->base
.width
, fb
->base
.height
, fb
->nvbo
->bo
.offset
, nvbo
);
405 vga_switcheroo_client_fb_set(dev
->pdev
, info
);
410 nouveau_vma_del(&fb
->vma
);
411 nouveau_bo_unmap(fb
->nvbo
);
413 nouveau_bo_unpin(fb
->nvbo
);
415 nouveau_bo_ref(NULL
, &fb
->nvbo
);
421 nouveau_fbcon_destroy(struct drm_device
*dev
, struct nouveau_fbdev
*fbcon
)
423 struct nouveau_framebuffer
*nouveau_fb
= nouveau_framebuffer(fbcon
->helper
.fb
);
425 drm_fb_helper_unregister_fbi(&fbcon
->helper
);
426 drm_fb_helper_fini(&fbcon
->helper
);
428 if (nouveau_fb
&& nouveau_fb
->nvbo
) {
429 nouveau_vma_del(&nouveau_fb
->vma
);
430 nouveau_bo_unmap(nouveau_fb
->nvbo
);
431 nouveau_bo_unpin(nouveau_fb
->nvbo
);
432 drm_framebuffer_put(&nouveau_fb
->base
);
438 void nouveau_fbcon_gpu_lockup(struct fb_info
*info
)
440 struct nouveau_fbdev
*fbcon
= info
->par
;
441 struct nouveau_drm
*drm
= nouveau_drm(fbcon
->helper
.dev
);
443 NV_ERROR(drm
, "GPU lockup - switching to software fbcon\n");
444 info
->flags
|= FBINFO_HWACCEL_DISABLED
;
447 static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs
= {
448 .fb_probe
= nouveau_fbcon_create
,
452 nouveau_fbcon_set_suspend_work(struct work_struct
*work
)
454 struct nouveau_drm
*drm
= container_of(work
, typeof(*drm
), fbcon_work
);
455 int state
= READ_ONCE(drm
->fbcon_new_state
);
457 if (state
== FBINFO_STATE_RUNNING
)
458 pm_runtime_get_sync(drm
->dev
->dev
);
461 if (state
== FBINFO_STATE_RUNNING
)
462 nouveau_fbcon_accel_restore(drm
->dev
);
463 drm_fb_helper_set_suspend(&drm
->fbcon
->helper
, state
);
464 if (state
!= FBINFO_STATE_RUNNING
)
465 nouveau_fbcon_accel_save_disable(drm
->dev
);
468 if (state
== FBINFO_STATE_RUNNING
) {
469 nouveau_fbcon_hotplug_resume(drm
->fbcon
);
470 pm_runtime_mark_last_busy(drm
->dev
->dev
);
471 pm_runtime_put_sync(drm
->dev
->dev
);
476 nouveau_fbcon_set_suspend(struct drm_device
*dev
, int state
)
478 struct nouveau_drm
*drm
= nouveau_drm(dev
);
483 drm
->fbcon_new_state
= state
;
484 /* Since runtime resume can happen as a result of a sysfs operation,
485 * it's possible we already have the console locked. So handle fbcon
486 * init/deinit from a seperate work thread
488 schedule_work(&drm
->fbcon_work
);
492 nouveau_fbcon_output_poll_changed(struct drm_device
*dev
)
494 struct nouveau_drm
*drm
= nouveau_drm(dev
);
495 struct nouveau_fbdev
*fbcon
= drm
->fbcon
;
501 mutex_lock(&fbcon
->hotplug_lock
);
503 ret
= pm_runtime_get(dev
->dev
);
504 if (ret
== 1 || ret
== -EACCES
) {
505 drm_fb_helper_hotplug_event(&fbcon
->helper
);
507 pm_runtime_mark_last_busy(dev
->dev
);
508 pm_runtime_put_autosuspend(dev
->dev
);
509 } else if (ret
== 0) {
510 /* If the GPU was already in the process of suspending before
511 * this event happened, then we can't block here as we'll
512 * deadlock the runtime pmops since they wait for us to
513 * finish. So, just defer this event for when we runtime
514 * resume again. It will be handled by fbcon_work.
516 NV_DEBUG(drm
, "fbcon HPD event deferred until runtime resume\n");
517 fbcon
->hotplug_waiting
= true;
518 pm_runtime_put_noidle(drm
->dev
->dev
);
520 DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
524 mutex_unlock(&fbcon
->hotplug_lock
);
528 nouveau_fbcon_hotplug_resume(struct nouveau_fbdev
*fbcon
)
530 struct nouveau_drm
*drm
;
534 drm
= nouveau_drm(fbcon
->helper
.dev
);
536 mutex_lock(&fbcon
->hotplug_lock
);
537 if (fbcon
->hotplug_waiting
) {
538 fbcon
->hotplug_waiting
= false;
540 NV_DEBUG(drm
, "Handling deferred fbcon HPD events\n");
541 drm_fb_helper_hotplug_event(&fbcon
->helper
);
543 mutex_unlock(&fbcon
->hotplug_lock
);
547 nouveau_fbcon_init(struct drm_device
*dev
)
549 struct nouveau_drm
*drm
= nouveau_drm(dev
);
550 struct nouveau_fbdev
*fbcon
;
551 int preferred_bpp
= nouveau_fbcon_bpp
;
554 if (!dev
->mode_config
.num_crtc
||
555 (dev
->pdev
->class >> 8) != PCI_CLASS_DISPLAY_VGA
)
558 fbcon
= kzalloc(sizeof(struct nouveau_fbdev
), GFP_KERNEL
);
563 INIT_WORK(&drm
->fbcon_work
, nouveau_fbcon_set_suspend_work
);
564 mutex_init(&fbcon
->hotplug_lock
);
566 drm_fb_helper_prepare(dev
, &fbcon
->helper
, &nouveau_fbcon_helper_funcs
);
568 ret
= drm_fb_helper_init(dev
, &fbcon
->helper
, 4);
572 ret
= drm_fb_helper_single_add_all_connectors(&fbcon
->helper
);
576 if (preferred_bpp
!= 8 && preferred_bpp
!= 16 && preferred_bpp
!= 32) {
577 if (drm
->client
.device
.info
.ram_size
<= 32 * 1024 * 1024)
580 if (drm
->client
.device
.info
.ram_size
<= 64 * 1024 * 1024)
586 /* disable all the possible outputs/crtcs before entering KMS mode */
587 if (!drm_drv_uses_atomic_modeset(dev
))
588 drm_helper_disable_unused_functions(dev
);
590 ret
= drm_fb_helper_initial_config(&fbcon
->helper
, preferred_bpp
);
594 if (fbcon
->helper
.fbdev
)
595 fbcon
->helper
.fbdev
->pixmap
.buf_align
= 4;
599 drm_fb_helper_fini(&fbcon
->helper
);
606 nouveau_fbcon_fini(struct drm_device
*dev
)
608 struct nouveau_drm
*drm
= nouveau_drm(dev
);
613 nouveau_fbcon_accel_fini(dev
);
614 nouveau_fbcon_destroy(dev
, drm
->fbcon
);