linux/drivers/gpu/drm/nouveau/nouveau_fbcon.c
<<
>>
Prefs
   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/fb.h>
  36#include <linux/init.h>
  37#include <linux/screen_info.h>
  38#include <linux/vga_switcheroo.h>
  39#include <linux/console.h>
  40
  41#include <drm/drmP.h>
  42#include <drm/drm_crtc.h>
  43#include <drm/drm_crtc_helper.h>
  44#include <drm/drm_fb_helper.h>
  45
  46#include "nouveau_drm.h"
  47#include "nouveau_gem.h"
  48#include "nouveau_bo.h"
  49#include "nouveau_fbcon.h"
  50#include "nouveau_chan.h"
  51
  52#include "nouveau_crtc.h"
  53
  54#include <core/client.h>
  55#include <core/device.h>
  56
  57#include <subdev/fb.h>
  58
  59MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
  60static int nouveau_nofbaccel = 0;
  61module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
  62
  63static void
  64nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
  65{
  66        struct nouveau_fbdev *fbcon = info->par;
  67        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
  68        struct nouveau_device *device = nv_device(drm->device);
  69        int ret;
  70
  71        if (info->state != FBINFO_STATE_RUNNING)
  72                return;
  73
  74        ret = -ENODEV;
  75        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
  76            mutex_trylock(&drm->client.mutex)) {
  77                if (device->card_type < NV_50)
  78                        ret = nv04_fbcon_fillrect(info, rect);
  79                else
  80                if (device->card_type < NV_C0)
  81                        ret = nv50_fbcon_fillrect(info, rect);
  82                else
  83                        ret = nvc0_fbcon_fillrect(info, rect);
  84                mutex_unlock(&drm->client.mutex);
  85        }
  86
  87        if (ret == 0)
  88                return;
  89
  90        if (ret != -ENODEV)
  91                nouveau_fbcon_gpu_lockup(info);
  92        cfb_fillrect(info, rect);
  93}
  94
  95static void
  96nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
  97{
  98        struct nouveau_fbdev *fbcon = info->par;
  99        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 100        struct nouveau_device *device = nv_device(drm->device);
 101        int ret;
 102
 103        if (info->state != FBINFO_STATE_RUNNING)
 104                return;
 105
 106        ret = -ENODEV;
 107        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
 108            mutex_trylock(&drm->client.mutex)) {
 109                if (device->card_type < NV_50)
 110                        ret = nv04_fbcon_copyarea(info, image);
 111                else
 112                if (device->card_type < NV_C0)
 113                        ret = nv50_fbcon_copyarea(info, image);
 114                else
 115                        ret = nvc0_fbcon_copyarea(info, image);
 116                mutex_unlock(&drm->client.mutex);
 117        }
 118
 119        if (ret == 0)
 120                return;
 121
 122        if (ret != -ENODEV)
 123                nouveau_fbcon_gpu_lockup(info);
 124        cfb_copyarea(info, image);
 125}
 126
 127static void
 128nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
 129{
 130        struct nouveau_fbdev *fbcon = info->par;
 131        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 132        struct nouveau_device *device = nv_device(drm->device);
 133        int ret;
 134
 135        if (info->state != FBINFO_STATE_RUNNING)
 136                return;
 137
 138        ret = -ENODEV;
 139        if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
 140            mutex_trylock(&drm->client.mutex)) {
 141                if (device->card_type < NV_50)
 142                        ret = nv04_fbcon_imageblit(info, image);
 143                else
 144                if (device->card_type < NV_C0)
 145                        ret = nv50_fbcon_imageblit(info, image);
 146                else
 147                        ret = nvc0_fbcon_imageblit(info, image);
 148                mutex_unlock(&drm->client.mutex);
 149        }
 150
 151        if (ret == 0)
 152                return;
 153
 154        if (ret != -ENODEV)
 155                nouveau_fbcon_gpu_lockup(info);
 156        cfb_imageblit(info, image);
 157}
 158
 159static int
 160nouveau_fbcon_sync(struct fb_info *info)
 161{
 162        struct nouveau_fbdev *fbcon = info->par;
 163        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 164        struct nouveau_channel *chan = drm->channel;
 165        int ret;
 166
 167        if (!chan || !chan->accel_done || in_interrupt() ||
 168            info->state != FBINFO_STATE_RUNNING ||
 169            info->flags & FBINFO_HWACCEL_DISABLED)
 170                return 0;
 171
 172        if (!mutex_trylock(&drm->client.mutex))
 173                return 0;
 174
 175        ret = nouveau_channel_idle(chan);
 176        mutex_unlock(&drm->client.mutex);
 177        if (ret) {
 178                nouveau_fbcon_gpu_lockup(info);
 179                return 0;
 180        }
 181
 182        chan->accel_done = false;
 183        return 0;
 184}
 185
 186static struct fb_ops nouveau_fbcon_ops = {
 187        .owner = THIS_MODULE,
 188        .fb_check_var = drm_fb_helper_check_var,
 189        .fb_set_par = drm_fb_helper_set_par,
 190        .fb_fillrect = nouveau_fbcon_fillrect,
 191        .fb_copyarea = nouveau_fbcon_copyarea,
 192        .fb_imageblit = nouveau_fbcon_imageblit,
 193        .fb_sync = nouveau_fbcon_sync,
 194        .fb_pan_display = drm_fb_helper_pan_display,
 195        .fb_blank = drm_fb_helper_blank,
 196        .fb_setcmap = drm_fb_helper_setcmap,
 197        .fb_debug_enter = drm_fb_helper_debug_enter,
 198        .fb_debug_leave = drm_fb_helper_debug_leave,
 199};
 200
 201static struct fb_ops nouveau_fbcon_sw_ops = {
 202        .owner = THIS_MODULE,
 203        .fb_check_var = drm_fb_helper_check_var,
 204        .fb_set_par = drm_fb_helper_set_par,
 205        .fb_fillrect = cfb_fillrect,
 206        .fb_copyarea = cfb_copyarea,
 207        .fb_imageblit = cfb_imageblit,
 208        .fb_pan_display = drm_fb_helper_pan_display,
 209        .fb_blank = drm_fb_helper_blank,
 210        .fb_setcmap = drm_fb_helper_setcmap,
 211        .fb_debug_enter = drm_fb_helper_debug_enter,
 212        .fb_debug_leave = drm_fb_helper_debug_leave,
 213};
 214
 215static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
 216                                    u16 blue, int regno)
 217{
 218        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 219
 220        nv_crtc->lut.r[regno] = red;
 221        nv_crtc->lut.g[regno] = green;
 222        nv_crtc->lut.b[regno] = blue;
 223}
 224
 225static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
 226                                    u16 *blue, int regno)
 227{
 228        struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
 229
 230        *red = nv_crtc->lut.r[regno];
 231        *green = nv_crtc->lut.g[regno];
 232        *blue = nv_crtc->lut.b[regno];
 233}
 234
 235static void
 236nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
 237{
 238        struct fb_info *info = fbcon->helper.fbdev;
 239        struct fb_fillrect rect;
 240
 241        /* Clear the entire fbcon.  The drm will program every connector
 242         * with it's preferred mode.  If the sizes differ, one display will
 243         * quite likely have garbage around the console.
 244         */
 245        rect.dx = rect.dy = 0;
 246        rect.width = info->var.xres_virtual;
 247        rect.height = info->var.yres_virtual;
 248        rect.color = 0;
 249        rect.rop = ROP_COPY;
 250        info->fbops->fb_fillrect(info, &rect);
 251}
 252
 253static int
 254nouveau_fbcon_create(struct drm_fb_helper *helper,
 255                     struct drm_fb_helper_surface_size *sizes)
 256{
 257        struct nouveau_fbdev *fbcon = (struct nouveau_fbdev *)helper;
 258        struct drm_device *dev = fbcon->dev;
 259        struct nouveau_drm *drm = nouveau_drm(dev);
 260        struct nouveau_device *device = nv_device(drm->device);
 261        struct fb_info *info;
 262        struct drm_framebuffer *fb;
 263        struct nouveau_framebuffer *nouveau_fb;
 264        struct nouveau_channel *chan;
 265        struct nouveau_bo *nvbo;
 266        struct drm_mode_fb_cmd2 mode_cmd;
 267        struct pci_dev *pdev = dev->pdev;
 268        int size, ret;
 269
 270        mode_cmd.width = sizes->surface_width;
 271        mode_cmd.height = sizes->surface_height;
 272
 273        mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
 274        mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
 275
 276        mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
 277                                                          sizes->surface_depth);
 278
 279        size = mode_cmd.pitches[0] * mode_cmd.height;
 280        size = roundup(size, PAGE_SIZE);
 281
 282        ret = nouveau_gem_new(dev, size, 0, NOUVEAU_GEM_DOMAIN_VRAM,
 283                              0, 0x0000, &nvbo);
 284        if (ret) {
 285                NV_ERROR(drm, "failed to allocate framebuffer\n");
 286                goto out;
 287        }
 288
 289        ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM);
 290        if (ret) {
 291                NV_ERROR(drm, "failed to pin fb: %d\n", ret);
 292                goto out_unref;
 293        }
 294
 295        ret = nouveau_bo_map(nvbo);
 296        if (ret) {
 297                NV_ERROR(drm, "failed to map fb: %d\n", ret);
 298                goto out_unpin;
 299        }
 300
 301        chan = nouveau_nofbaccel ? NULL : drm->channel;
 302        if (chan && device->card_type >= NV_50) {
 303                ret = nouveau_bo_vma_add(nvbo, nv_client(chan->cli)->vm,
 304                                        &fbcon->nouveau_fb.vma);
 305                if (ret) {
 306                        NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
 307                        chan = NULL;
 308                }
 309        }
 310
 311        mutex_lock(&dev->struct_mutex);
 312
 313        info = framebuffer_alloc(0, &pdev->dev);
 314        if (!info) {
 315                ret = -ENOMEM;
 316                goto out_unlock;
 317        }
 318
 319        ret = fb_alloc_cmap(&info->cmap, 256, 0);
 320        if (ret) {
 321                ret = -ENOMEM;
 322                framebuffer_release(info);
 323                goto out_unlock;
 324        }
 325
 326        info->par = fbcon;
 327
 328        nouveau_framebuffer_init(dev, &fbcon->nouveau_fb, &mode_cmd, nvbo);
 329
 330        nouveau_fb = &fbcon->nouveau_fb;
 331        fb = &nouveau_fb->base;
 332
 333        /* setup helper */
 334        fbcon->helper.fb = fb;
 335        fbcon->helper.fbdev = info;
 336
 337        strcpy(info->fix.id, "nouveaufb");
 338        if (!chan)
 339                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
 340        else
 341                info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
 342                              FBINFO_HWACCEL_FILLRECT |
 343                              FBINFO_HWACCEL_IMAGEBLIT;
 344        info->flags |= FBINFO_CAN_FORCE_OUTPUT;
 345        info->fbops = &nouveau_fbcon_sw_ops;
 346        info->fix.smem_start = nvbo->bo.mem.bus.base +
 347                               nvbo->bo.mem.bus.offset;
 348        info->fix.smem_len = size;
 349
 350        info->screen_base = nvbo_kmap_obj_iovirtual(nouveau_fb->nvbo);
 351        info->screen_size = size;
 352
 353        drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
 354        drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
 355
 356        /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
 357
 358        mutex_unlock(&dev->struct_mutex);
 359
 360        if (chan) {
 361                ret = -ENODEV;
 362                if (device->card_type < NV_50)
 363                        ret = nv04_fbcon_accel_init(info);
 364                else
 365                if (device->card_type < NV_C0)
 366                        ret = nv50_fbcon_accel_init(info);
 367                else
 368                        ret = nvc0_fbcon_accel_init(info);
 369
 370                if (ret == 0)
 371                        info->fbops = &nouveau_fbcon_ops;
 372        }
 373
 374        nouveau_fbcon_zfill(dev, fbcon);
 375
 376        /* To allow resizeing without swapping buffers */
 377        NV_INFO(drm, "allocated %dx%d fb: 0x%lx, bo %p\n",
 378                nouveau_fb->base.width, nouveau_fb->base.height,
 379                nvbo->bo.offset, nvbo);
 380
 381        vga_switcheroo_client_fb_set(dev->pdev, info);
 382        return 0;
 383
 384out_unlock:
 385        mutex_unlock(&dev->struct_mutex);
 386        if (chan)
 387                nouveau_bo_vma_del(nvbo, &fbcon->nouveau_fb.vma);
 388        nouveau_bo_unmap(nvbo);
 389out_unpin:
 390        nouveau_bo_unpin(nvbo);
 391out_unref:
 392        nouveau_bo_ref(NULL, &nvbo);
 393out:
 394        return ret;
 395}
 396
 397void
 398nouveau_fbcon_output_poll_changed(struct drm_device *dev)
 399{
 400        struct nouveau_drm *drm = nouveau_drm(dev);
 401        if (drm->fbcon)
 402                drm_fb_helper_hotplug_event(&drm->fbcon->helper);
 403}
 404
 405static int
 406nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
 407{
 408        struct nouveau_framebuffer *nouveau_fb = &fbcon->nouveau_fb;
 409        struct fb_info *info;
 410
 411        if (fbcon->helper.fbdev) {
 412                info = fbcon->helper.fbdev;
 413                unregister_framebuffer(info);
 414                if (info->cmap.len)
 415                        fb_dealloc_cmap(&info->cmap);
 416                framebuffer_release(info);
 417        }
 418
 419        if (nouveau_fb->nvbo) {
 420                nouveau_bo_unmap(nouveau_fb->nvbo);
 421                nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
 422                nouveau_bo_unpin(nouveau_fb->nvbo);
 423                drm_gem_object_unreference_unlocked(nouveau_fb->nvbo->gem);
 424                nouveau_fb->nvbo = NULL;
 425        }
 426        drm_fb_helper_fini(&fbcon->helper);
 427        drm_framebuffer_unregister_private(&nouveau_fb->base);
 428        drm_framebuffer_cleanup(&nouveau_fb->base);
 429        return 0;
 430}
 431
 432void nouveau_fbcon_gpu_lockup(struct fb_info *info)
 433{
 434        struct nouveau_fbdev *fbcon = info->par;
 435        struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
 436
 437        NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
 438        info->flags |= FBINFO_HWACCEL_DISABLED;
 439}
 440
 441static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
 442        .gamma_set = nouveau_fbcon_gamma_set,
 443        .gamma_get = nouveau_fbcon_gamma_get,
 444        .fb_probe = nouveau_fbcon_create,
 445};
 446
 447
 448int
 449nouveau_fbcon_init(struct drm_device *dev)
 450{
 451        struct nouveau_drm *drm = nouveau_drm(dev);
 452        struct nouveau_fb *pfb = nouveau_fb(drm->device);
 453        struct nouveau_fbdev *fbcon;
 454        int preferred_bpp;
 455        int ret;
 456
 457        if (!dev->mode_config.num_crtc)
 458                return 0;
 459
 460        fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
 461        if (!fbcon)
 462                return -ENOMEM;
 463
 464        fbcon->dev = dev;
 465        drm->fbcon = fbcon;
 466        fbcon->helper.funcs = &nouveau_fbcon_helper_funcs;
 467
 468        ret = drm_fb_helper_init(dev, &fbcon->helper,
 469                                 dev->mode_config.num_crtc, 4);
 470        if (ret) {
 471                kfree(fbcon);
 472                return ret;
 473        }
 474
 475        drm_fb_helper_single_add_all_connectors(&fbcon->helper);
 476
 477        if (pfb->ram->size <= 32 * 1024 * 1024)
 478                preferred_bpp = 8;
 479        else
 480        if (pfb->ram->size <= 64 * 1024 * 1024)
 481                preferred_bpp = 16;
 482        else
 483                preferred_bpp = 32;
 484
 485        /* disable all the possible outputs/crtcs before entering KMS mode */
 486        drm_helper_disable_unused_functions(dev);
 487
 488        drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
 489        return 0;
 490}
 491
 492void
 493nouveau_fbcon_fini(struct drm_device *dev)
 494{
 495        struct nouveau_drm *drm = nouveau_drm(dev);
 496
 497        if (!drm->fbcon)
 498                return;
 499
 500        nouveau_fbcon_destroy(dev, drm->fbcon);
 501        kfree(drm->fbcon);
 502        drm->fbcon = NULL;
 503}
 504
 505void nouveau_fbcon_save_disable_accel(struct drm_device *dev)
 506{
 507        struct nouveau_drm *drm = nouveau_drm(dev);
 508
 509        drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
 510        drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
 511}
 512
 513void nouveau_fbcon_restore_accel(struct drm_device *dev)
 514{
 515        struct nouveau_drm *drm = nouveau_drm(dev);
 516        drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
 517}
 518
 519void nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
 520{
 521        struct nouveau_drm *drm = nouveau_drm(dev);
 522        console_lock();
 523        if (state == 0)
 524                nouveau_fbcon_save_disable_accel(dev);
 525        fb_set_suspend(drm->fbcon->helper.fbdev, state);
 526        if (state == 1)
 527                nouveau_fbcon_restore_accel(dev);
 528        console_unlock();
 529}
 530
 531void nouveau_fbcon_zfill_all(struct drm_device *dev)
 532{
 533        struct nouveau_drm *drm = nouveau_drm(dev);
 534        nouveau_fbcon_zfill(dev, drm->fbcon);
 535}
 536