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