]> asedeno.scripts.mit.edu Git - linux.git/blob - drivers/gpu/drm/nouveau/nouveau_fbcon.c
Merge branches 'pm-core', 'pm-qos', 'pm-domains' and 'pm-opp'
[linux.git] / drivers / gpu / drm / nouveau / nouveau_fbcon.c
1 /*
2  * Copyright © 2007 David Airlie
3  *
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:
10  *
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
13  * Software.
14  *
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.
22  *
23  * Authors:
24  *     David Airlie
25  */
26
27 #include <linux/module.h>
28 #include <linux/kernel.h>
29 #include <linux/errno.h>
30 #include <linux/string.h>
31 #include <linux/mm.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>
39
40 #include <drm/drmP.h>
41 #include <drm/drm_crtc.h>
42 #include <drm/drm_crtc_helper.h>
43 #include <drm/drm_fb_helper.h>
44
45 #include "nouveau_drv.h"
46 #include "nouveau_gem.h"
47 #include "nouveau_bo.h"
48 #include "nouveau_fbcon.h"
49 #include "nouveau_chan.h"
50
51 #include "nouveau_crtc.h"
52
53 MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
54 int nouveau_nofbaccel = 0;
55 module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
56
57 static void
58 nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
59 {
60         struct nouveau_fbdev *fbcon = info->par;
61         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
62         struct nvif_device *device = &drm->device;
63         int ret;
64
65         if (info->state != FBINFO_STATE_RUNNING)
66                 return;
67
68         ret = -ENODEV;
69         if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
70             mutex_trylock(&drm->client.mutex)) {
71                 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
72                         ret = nv04_fbcon_fillrect(info, rect);
73                 else
74                 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
75                         ret = nv50_fbcon_fillrect(info, rect);
76                 else
77                         ret = nvc0_fbcon_fillrect(info, rect);
78                 mutex_unlock(&drm->client.mutex);
79         }
80
81         if (ret == 0)
82                 return;
83
84         if (ret != -ENODEV)
85                 nouveau_fbcon_gpu_lockup(info);
86         drm_fb_helper_cfb_fillrect(info, rect);
87 }
88
89 static void
90 nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
91 {
92         struct nouveau_fbdev *fbcon = info->par;
93         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
94         struct nvif_device *device = &drm->device;
95         int ret;
96
97         if (info->state != FBINFO_STATE_RUNNING)
98                 return;
99
100         ret = -ENODEV;
101         if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
102             mutex_trylock(&drm->client.mutex)) {
103                 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
104                         ret = nv04_fbcon_copyarea(info, image);
105                 else
106                 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
107                         ret = nv50_fbcon_copyarea(info, image);
108                 else
109                         ret = nvc0_fbcon_copyarea(info, image);
110                 mutex_unlock(&drm->client.mutex);
111         }
112
113         if (ret == 0)
114                 return;
115
116         if (ret != -ENODEV)
117                 nouveau_fbcon_gpu_lockup(info);
118         drm_fb_helper_cfb_copyarea(info, image);
119 }
120
121 static void
122 nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
123 {
124         struct nouveau_fbdev *fbcon = info->par;
125         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
126         struct nvif_device *device = &drm->device;
127         int ret;
128
129         if (info->state != FBINFO_STATE_RUNNING)
130                 return;
131
132         ret = -ENODEV;
133         if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
134             mutex_trylock(&drm->client.mutex)) {
135                 if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
136                         ret = nv04_fbcon_imageblit(info, image);
137                 else
138                 if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
139                         ret = nv50_fbcon_imageblit(info, image);
140                 else
141                         ret = nvc0_fbcon_imageblit(info, image);
142                 mutex_unlock(&drm->client.mutex);
143         }
144
145         if (ret == 0)
146                 return;
147
148         if (ret != -ENODEV)
149                 nouveau_fbcon_gpu_lockup(info);
150         drm_fb_helper_cfb_imageblit(info, image);
151 }
152
153 static int
154 nouveau_fbcon_sync(struct fb_info *info)
155 {
156         struct nouveau_fbdev *fbcon = info->par;
157         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
158         struct nouveau_channel *chan = drm->channel;
159         int ret;
160
161         if (!chan || !chan->accel_done || in_interrupt() ||
162             info->state != FBINFO_STATE_RUNNING ||
163             info->flags & FBINFO_HWACCEL_DISABLED)
164                 return 0;
165
166         if (!mutex_trylock(&drm->client.mutex))
167                 return 0;
168
169         ret = nouveau_channel_idle(chan);
170         mutex_unlock(&drm->client.mutex);
171         if (ret) {
172                 nouveau_fbcon_gpu_lockup(info);
173                 return 0;
174         }
175
176         chan->accel_done = false;
177         return 0;
178 }
179
180 static int
181 nouveau_fbcon_open(struct fb_info *info, int user)
182 {
183         struct nouveau_fbdev *fbcon = info->par;
184         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
185         int ret = pm_runtime_get_sync(drm->dev->dev);
186         if (ret < 0 && ret != -EACCES)
187                 return ret;
188         return 0;
189 }
190
191 static int
192 nouveau_fbcon_release(struct fb_info *info, int user)
193 {
194         struct nouveau_fbdev *fbcon = info->par;
195         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
196         pm_runtime_put(drm->dev->dev);
197         return 0;
198 }
199
200 static struct fb_ops nouveau_fbcon_ops = {
201         .owner = THIS_MODULE,
202         DRM_FB_HELPER_DEFAULT_OPS,
203         .fb_open = nouveau_fbcon_open,
204         .fb_release = nouveau_fbcon_release,
205         .fb_fillrect = nouveau_fbcon_fillrect,
206         .fb_copyarea = nouveau_fbcon_copyarea,
207         .fb_imageblit = nouveau_fbcon_imageblit,
208         .fb_sync = nouveau_fbcon_sync,
209 };
210
211 static struct fb_ops nouveau_fbcon_sw_ops = {
212         .owner = THIS_MODULE,
213         DRM_FB_HELPER_DEFAULT_OPS,
214         .fb_open = nouveau_fbcon_open,
215         .fb_release = nouveau_fbcon_release,
216         .fb_fillrect = drm_fb_helper_cfb_fillrect,
217         .fb_copyarea = drm_fb_helper_cfb_copyarea,
218         .fb_imageblit = drm_fb_helper_cfb_imageblit,
219 };
220
221 void
222 nouveau_fbcon_accel_save_disable(struct drm_device *dev)
223 {
224         struct nouveau_drm *drm = nouveau_drm(dev);
225         if (drm->fbcon) {
226                 drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
227                 drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
228         }
229 }
230
231 void
232 nouveau_fbcon_accel_restore(struct drm_device *dev)
233 {
234         struct nouveau_drm *drm = nouveau_drm(dev);
235         if (drm->fbcon) {
236                 drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
237         }
238 }
239
240 static void
241 nouveau_fbcon_accel_fini(struct drm_device *dev)
242 {
243         struct nouveau_drm *drm = nouveau_drm(dev);
244         struct nouveau_fbdev *fbcon = drm->fbcon;
245         if (fbcon && drm->channel) {
246                 console_lock();
247                 fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
248                 console_unlock();
249                 nouveau_channel_idle(drm->channel);
250                 nvif_object_fini(&fbcon->twod);
251                 nvif_object_fini(&fbcon->blit);
252                 nvif_object_fini(&fbcon->gdi);
253                 nvif_object_fini(&fbcon->patt);
254                 nvif_object_fini(&fbcon->rop);
255                 nvif_object_fini(&fbcon->clip);
256                 nvif_object_fini(&fbcon->surf2d);
257         }
258 }
259
260 static void
261 nouveau_fbcon_accel_init(struct drm_device *dev)
262 {
263         struct nouveau_drm *drm = nouveau_drm(dev);
264         struct nouveau_fbdev *fbcon = drm->fbcon;
265         struct fb_info *info = fbcon->helper.fbdev;
266         int ret;
267
268         if (drm->device.info.family < NV_DEVICE_INFO_V0_TESLA)
269                 ret = nv04_fbcon_accel_init(info);
270         else
271         if (drm->device.info.family < NV_DEVICE_INFO_V0_FERMI)
272                 ret = nv50_fbcon_accel_init(info);
273         else
274                 ret = nvc0_fbcon_accel_init(info);
275
276         if (ret == 0)
277                 info->fbops = &nouveau_fbcon_ops;
278 }
279
280 static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
281                                     u16 blue, int regno)
282 {
283         struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
284
285         nv_crtc->lut.r[regno] = red;
286         nv_crtc->lut.g[regno] = green;
287         nv_crtc->lut.b[regno] = blue;
288 }
289
290 static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
291                                     u16 *blue, int regno)
292 {
293         struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
294
295         *red = nv_crtc->lut.r[regno];
296         *green = nv_crtc->lut.g[regno];
297         *blue = nv_crtc->lut.b[regno];
298 }
299
300 static void
301 nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
302 {
303         struct fb_info *info = fbcon->helper.fbdev;
304         struct fb_fillrect rect;
305
306         /* Clear the entire fbcon.  The drm will program every connector
307          * with it's preferred mode.  If the sizes differ, one display will
308          * quite likely have garbage around the console.
309          */
310         rect.dx = rect.dy = 0;
311         rect.width = info->var.xres_virtual;
312         rect.height = info->var.yres_virtual;
313         rect.color = 0;
314         rect.rop = ROP_COPY;
315         info->fbops->fb_fillrect(info, &rect);
316 }
317
318 static int
319 nouveau_fbcon_create(struct drm_fb_helper *helper,
320                      struct drm_fb_helper_surface_size *sizes)
321 {
322         struct nouveau_fbdev *fbcon =
323                 container_of(helper, struct nouveau_fbdev, helper);
324         struct drm_device *dev = fbcon->helper.dev;
325         struct nouveau_drm *drm = nouveau_drm(dev);
326         struct nvif_device *device = &drm->device;
327         struct fb_info *info;
328         struct nouveau_framebuffer *fb;
329         struct nouveau_channel *chan;
330         struct nouveau_bo *nvbo;
331         struct drm_mode_fb_cmd2 mode_cmd;
332         int ret;
333
334         mode_cmd.width = sizes->surface_width;
335         mode_cmd.height = sizes->surface_height;
336
337         mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
338         mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
339
340         mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
341                                                           sizes->surface_depth);
342
343         ret = nouveau_gem_new(dev, mode_cmd.pitches[0] * mode_cmd.height,
344                               0, NOUVEAU_GEM_DOMAIN_VRAM, 0, 0x0000, &nvbo);
345         if (ret) {
346                 NV_ERROR(drm, "failed to allocate framebuffer\n");
347                 goto out;
348         }
349
350         ret = nouveau_framebuffer_new(dev, &mode_cmd, nvbo, &fb);
351         if (ret)
352                 goto out_unref;
353
354         ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM, false);
355         if (ret) {
356                 NV_ERROR(drm, "failed to pin fb: %d\n", ret);
357                 goto out_unref;
358         }
359
360         ret = nouveau_bo_map(nvbo);
361         if (ret) {
362                 NV_ERROR(drm, "failed to map fb: %d\n", ret);
363                 goto out_unpin;
364         }
365
366         chan = nouveau_nofbaccel ? NULL : drm->channel;
367         if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
368                 ret = nouveau_bo_vma_add(nvbo, drm->client.vm, &fb->vma);
369                 if (ret) {
370                         NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
371                         chan = NULL;
372                 }
373         }
374
375         info = drm_fb_helper_alloc_fbi(helper);
376         if (IS_ERR(info)) {
377                 ret = PTR_ERR(info);
378                 goto out_unlock;
379         }
380         info->skip_vt_switch = 1;
381
382         info->par = fbcon;
383
384         /* setup helper */
385         fbcon->helper.fb = &fb->base;
386
387         strcpy(info->fix.id, "nouveaufb");
388         if (!chan)
389                 info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
390         else
391                 info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
392                               FBINFO_HWACCEL_FILLRECT |
393                               FBINFO_HWACCEL_IMAGEBLIT;
394         info->flags |= FBINFO_CAN_FORCE_OUTPUT;
395         info->fbops = &nouveau_fbcon_sw_ops;
396         info->fix.smem_start = fb->nvbo->bo.mem.bus.base +
397                                fb->nvbo->bo.mem.bus.offset;
398         info->fix.smem_len = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
399
400         info->screen_base = nvbo_kmap_obj_iovirtual(fb->nvbo);
401         info->screen_size = fb->nvbo->bo.mem.num_pages << PAGE_SHIFT;
402
403         drm_fb_helper_fill_fix(info, fb->base.pitches[0], fb->base.depth);
404         drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
405
406         /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
407
408         if (chan)
409                 nouveau_fbcon_accel_init(dev);
410         nouveau_fbcon_zfill(dev, fbcon);
411
412         /* To allow resizeing without swapping buffers */
413         NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
414                 fb->base.width, fb->base.height, fb->nvbo->bo.offset, nvbo);
415
416         vga_switcheroo_client_fb_set(dev->pdev, info);
417         return 0;
418
419 out_unlock:
420         if (chan)
421                 nouveau_bo_vma_del(fb->nvbo, &fb->vma);
422         nouveau_bo_unmap(fb->nvbo);
423 out_unpin:
424         nouveau_bo_unpin(fb->nvbo);
425 out_unref:
426         nouveau_bo_ref(NULL, &fb->nvbo);
427 out:
428         return ret;
429 }
430
431 void
432 nouveau_fbcon_output_poll_changed(struct drm_device *dev)
433 {
434         struct nouveau_drm *drm = nouveau_drm(dev);
435         if (drm->fbcon)
436                 drm_fb_helper_hotplug_event(&drm->fbcon->helper);
437 }
438
439 static int
440 nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
441 {
442         struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fbcon->helper.fb);
443
444         drm_fb_helper_unregister_fbi(&fbcon->helper);
445         drm_fb_helper_release_fbi(&fbcon->helper);
446         drm_fb_helper_fini(&fbcon->helper);
447
448         if (nouveau_fb->nvbo) {
449                 nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
450                 nouveau_bo_unmap(nouveau_fb->nvbo);
451                 nouveau_bo_unpin(nouveau_fb->nvbo);
452                 drm_framebuffer_unreference(&nouveau_fb->base);
453         }
454
455         return 0;
456 }
457
458 void nouveau_fbcon_gpu_lockup(struct fb_info *info)
459 {
460         struct nouveau_fbdev *fbcon = info->par;
461         struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
462
463         NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
464         info->flags |= FBINFO_HWACCEL_DISABLED;
465 }
466
467 static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
468         .gamma_set = nouveau_fbcon_gamma_set,
469         .gamma_get = nouveau_fbcon_gamma_get,
470         .fb_probe = nouveau_fbcon_create,
471 };
472
473 static void
474 nouveau_fbcon_set_suspend_work(struct work_struct *work)
475 {
476         struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
477         int state = READ_ONCE(drm->fbcon_new_state);
478
479         if (state == FBINFO_STATE_RUNNING)
480                 pm_runtime_get_sync(drm->dev->dev);
481
482         console_lock();
483         if (state == FBINFO_STATE_RUNNING)
484                 nouveau_fbcon_accel_restore(drm->dev);
485         drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
486         if (state != FBINFO_STATE_RUNNING)
487                 nouveau_fbcon_accel_save_disable(drm->dev);
488         console_unlock();
489
490         if (state == FBINFO_STATE_RUNNING) {
491                 pm_runtime_mark_last_busy(drm->dev->dev);
492                 pm_runtime_put_sync(drm->dev->dev);
493         }
494 }
495
496 void
497 nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
498 {
499         struct nouveau_drm *drm = nouveau_drm(dev);
500
501         if (!drm->fbcon)
502                 return;
503
504         drm->fbcon_new_state = state;
505         /* Since runtime resume can happen as a result of a sysfs operation,
506          * it's possible we already have the console locked. So handle fbcon
507          * init/deinit from a seperate work thread
508          */
509         schedule_work(&drm->fbcon_work);
510 }
511
512 int
513 nouveau_fbcon_init(struct drm_device *dev)
514 {
515         struct nouveau_drm *drm = nouveau_drm(dev);
516         struct nouveau_fbdev *fbcon;
517         int preferred_bpp;
518         int ret;
519
520         if (!dev->mode_config.num_crtc ||
521             (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
522                 return 0;
523
524         fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
525         if (!fbcon)
526                 return -ENOMEM;
527
528         drm->fbcon = fbcon;
529         INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
530
531         drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
532
533         ret = drm_fb_helper_init(dev, &fbcon->helper,
534                                  dev->mode_config.num_crtc, 4);
535         if (ret)
536                 goto free;
537
538         ret = drm_fb_helper_single_add_all_connectors(&fbcon->helper);
539         if (ret)
540                 goto fini;
541
542         if (drm->device.info.ram_size <= 32 * 1024 * 1024)
543                 preferred_bpp = 8;
544         else
545         if (drm->device.info.ram_size <= 64 * 1024 * 1024)
546                 preferred_bpp = 16;
547         else
548                 preferred_bpp = 32;
549
550         /* disable all the possible outputs/crtcs before entering KMS mode */
551         if (!dev->mode_config.funcs->atomic_commit)
552                 drm_helper_disable_unused_functions(dev);
553
554         ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
555         if (ret)
556                 goto fini;
557
558         if (fbcon->helper.fbdev)
559                 fbcon->helper.fbdev->pixmap.buf_align = 4;
560         return 0;
561
562 fini:
563         drm_fb_helper_fini(&fbcon->helper);
564 free:
565         kfree(fbcon);
566         return ret;
567 }
568
569 void
570 nouveau_fbcon_fini(struct drm_device *dev)
571 {
572         struct nouveau_drm *drm = nouveau_drm(dev);
573
574         if (!drm->fbcon)
575                 return;
576
577         nouveau_fbcon_accel_fini(dev);
578         nouveau_fbcon_destroy(dev, drm->fbcon);
579         kfree(drm->fbcon);
580         drm->fbcon = NULL;
581 }