nouveau_fbcon.c 14.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
/*
 * Copyright © 2007 David Airlie
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *     David Airlie
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/tty.h>
#include <linux/sysrq.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/screen_info.h>
38
#include <linux/vga_switcheroo.h>
39
#include <linux/console.h>
40

41 42 43 44
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fb_helper.h>
45 46 47 48

#include "nouveau_drm.h"
#include "nouveau_gem.h"
#include "nouveau_bo.h"
49
#include "nouveau_fbcon.h"
50 51 52 53 54 55 56
#include "nouveau_chan.h"

#include "nouveau_crtc.h"

MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
static int nouveau_nofbaccel = 0;
module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
57

58 59 60
static void
nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
{
61
	struct nouveau_fbdev *fbcon = info->par;
62
	struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
63
	struct nvif_device *device = &drm->device;
64 65 66 67 68 69
	int ret;

	if (info->state != FBINFO_STATE_RUNNING)
		return;

	ret = -ENODEV;
70
	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
71
	    mutex_trylock(&drm->client.mutex)) {
72
		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
73 74
			ret = nv04_fbcon_fillrect(info, rect);
		else
75
		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
76
			ret = nv50_fbcon_fillrect(info, rect);
77 78
		else
			ret = nvc0_fbcon_fillrect(info, rect);
79
		mutex_unlock(&drm->client.mutex);
80 81 82 83 84 85 86 87 88 89 90 91 92
	}

	if (ret == 0)
		return;

	if (ret != -ENODEV)
		nouveau_fbcon_gpu_lockup(info);
	cfb_fillrect(info, rect);
}

static void
nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
{
93
	struct nouveau_fbdev *fbcon = info->par;
94
	struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
95
	struct nvif_device *device = &drm->device;
96 97 98 99 100 101
	int ret;

	if (info->state != FBINFO_STATE_RUNNING)
		return;

	ret = -ENODEV;
102
	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
103
	    mutex_trylock(&drm->client.mutex)) {
104
		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
105 106
			ret = nv04_fbcon_copyarea(info, image);
		else
107
		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
108
			ret = nv50_fbcon_copyarea(info, image);
109 110
		else
			ret = nvc0_fbcon_copyarea(info, image);
111
		mutex_unlock(&drm->client.mutex);
112 113 114 115 116 117 118 119 120 121 122 123 124
	}

	if (ret == 0)
		return;

	if (ret != -ENODEV)
		nouveau_fbcon_gpu_lockup(info);
	cfb_copyarea(info, image);
}

static void
nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
{
125
	struct nouveau_fbdev *fbcon = info->par;
126
	struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
127
	struct nvif_device *device = &drm->device;
128 129 130 131 132 133
	int ret;

	if (info->state != FBINFO_STATE_RUNNING)
		return;

	ret = -ENODEV;
134
	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
135
	    mutex_trylock(&drm->client.mutex)) {
136
		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
137 138
			ret = nv04_fbcon_imageblit(info, image);
		else
139
		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
140
			ret = nv50_fbcon_imageblit(info, image);
141 142
		else
			ret = nvc0_fbcon_imageblit(info, image);
143
		mutex_unlock(&drm->client.mutex);
144 145 146 147 148 149 150 151 152 153
	}

	if (ret == 0)
		return;

	if (ret != -ENODEV)
		nouveau_fbcon_gpu_lockup(info);
	cfb_imageblit(info, image);
}

154 155 156
static int
nouveau_fbcon_sync(struct fb_info *info)
{
157
	struct nouveau_fbdev *fbcon = info->par;
158
	struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
159
	struct nouveau_channel *chan = drm->channel;
160
	int ret;
161

162
	if (!chan || !chan->accel_done || in_interrupt() ||
163 164 165 166
	    info->state != FBINFO_STATE_RUNNING ||
	    info->flags & FBINFO_HWACCEL_DISABLED)
		return 0;

167
	if (!mutex_trylock(&drm->client.mutex))
168 169
		return 0;

170
	ret = nouveau_channel_idle(chan);
171
	mutex_unlock(&drm->client.mutex);
172
	if (ret) {
173
		nouveau_fbcon_gpu_lockup(info);
174 175 176 177 178 179 180 181 182 183 184
		return 0;
	}

	chan->accel_done = false;
	return 0;
}

static struct fb_ops nouveau_fbcon_ops = {
	.owner = THIS_MODULE,
	.fb_check_var = drm_fb_helper_check_var,
	.fb_set_par = drm_fb_helper_set_par,
185 186 187
	.fb_fillrect = nouveau_fbcon_fillrect,
	.fb_copyarea = nouveau_fbcon_copyarea,
	.fb_imageblit = nouveau_fbcon_imageblit,
188 189 190 191
	.fb_sync = nouveau_fbcon_sync,
	.fb_pan_display = drm_fb_helper_pan_display,
	.fb_blank = drm_fb_helper_blank,
	.fb_setcmap = drm_fb_helper_setcmap,
192 193
	.fb_debug_enter = drm_fb_helper_debug_enter,
	.fb_debug_leave = drm_fb_helper_debug_leave,
194 195
};

196
static struct fb_ops nouveau_fbcon_sw_ops = {
197 198 199
	.owner = THIS_MODULE,
	.fb_check_var = drm_fb_helper_check_var,
	.fb_set_par = drm_fb_helper_set_par,
200 201 202
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
203 204 205
	.fb_pan_display = drm_fb_helper_pan_display,
	.fb_blank = drm_fb_helper_blank,
	.fb_setcmap = drm_fb_helper_setcmap,
206 207
	.fb_debug_enter = drm_fb_helper_debug_enter,
	.fb_debug_leave = drm_fb_helper_debug_leave,
208 209
};

210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
void
nouveau_fbcon_accel_save_disable(struct drm_device *dev)
{
	struct nouveau_drm *drm = nouveau_drm(dev);
	if (drm->fbcon) {
		drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
		drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
	}
}

void
nouveau_fbcon_accel_restore(struct drm_device *dev)
{
	struct nouveau_drm *drm = nouveau_drm(dev);
	if (drm->fbcon) {
		drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
	}
}

void
nouveau_fbcon_accel_fini(struct drm_device *dev)
{
	struct nouveau_drm *drm = nouveau_drm(dev);
	struct nouveau_fbdev *fbcon = drm->fbcon;
	if (fbcon && drm->channel) {
		console_lock();
		fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
		console_unlock();
		nouveau_channel_idle(drm->channel);
239 240 241 242 243 244 245
		nvif_object_fini(&fbcon->twod);
		nvif_object_fini(&fbcon->blit);
		nvif_object_fini(&fbcon->gdi);
		nvif_object_fini(&fbcon->patt);
		nvif_object_fini(&fbcon->rop);
		nvif_object_fini(&fbcon->clip);
		nvif_object_fini(&fbcon->surf2d);
246 247 248 249 250 251 252 253 254 255 256
	}
}

void
nouveau_fbcon_accel_init(struct drm_device *dev)
{
	struct nouveau_drm *drm = nouveau_drm(dev);
	struct nouveau_fbdev *fbcon = drm->fbcon;
	struct fb_info *info = fbcon->helper.fbdev;
	int ret;

257
	if (drm->device.info.family < NV_DEVICE_INFO_V0_TESLA)
258 259
		ret = nv04_fbcon_accel_init(info);
	else
260
	if (drm->device.info.family < NV_DEVICE_INFO_V0_FERMI)
261 262 263 264 265 266 267 268
		ret = nv50_fbcon_accel_init(info);
	else
		ret = nvc0_fbcon_accel_init(info);

	if (ret == 0)
		info->fbops = &nouveau_fbcon_ops;
}

269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
				    u16 blue, int regno)
{
	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);

	nv_crtc->lut.r[regno] = red;
	nv_crtc->lut.g[regno] = green;
	nv_crtc->lut.b[regno] = blue;
}

static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
				    u16 *blue, int regno)
{
	struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);

	*red = nv_crtc->lut.r[regno];
	*green = nv_crtc->lut.g[regno];
	*blue = nv_crtc->lut.b[regno];
}

289
static void
290
nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
291
{
292
	struct fb_info *info = fbcon->helper.fbdev;
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307
	struct fb_fillrect rect;

	/* Clear the entire fbcon.  The drm will program every connector
	 * with it's preferred mode.  If the sizes differ, one display will
	 * quite likely have garbage around the console.
	 */
	rect.dx = rect.dy = 0;
	rect.width = info->var.xres_virtual;
	rect.height = info->var.yres_virtual;
	rect.color = 0;
	rect.rop = ROP_COPY;
	info->fbops->fb_fillrect(info, &rect);
}

static int
308
nouveau_fbcon_create(struct drm_fb_helper *helper,
309
		     struct drm_fb_helper_surface_size *sizes)
310
{
311
	struct nouveau_fbdev *fbcon = (struct nouveau_fbdev *)helper;
312
	struct drm_device *dev = fbcon->dev;
313
	struct nouveau_drm *drm = nouveau_drm(dev);
314
	struct nvif_device *device = &drm->device;
315 316 317
	struct fb_info *info;
	struct drm_framebuffer *fb;
	struct nouveau_framebuffer *nouveau_fb;
318
	struct nouveau_channel *chan;
319
	struct nouveau_bo *nvbo;
320
	struct drm_mode_fb_cmd2 mode_cmd;
321
	struct pci_dev *pdev = dev->pdev;
322 323
	int size, ret;

324 325
	mode_cmd.width = sizes->surface_width;
	mode_cmd.height = sizes->surface_height;
326

327 328
	mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
	mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
329

330 331 332 333
	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
							  sizes->surface_depth);

	size = mode_cmd.pitches[0] * mode_cmd.height;
334
	size = roundup(size, PAGE_SIZE);
335

336 337
	ret = nouveau_gem_new(dev, size, 0, NOUVEAU_GEM_DOMAIN_VRAM,
			      0, 0x0000, &nvbo);
338
	if (ret) {
339
		NV_ERROR(drm, "failed to allocate framebuffer\n");
340 341 342 343 344
		goto out;
	}

	ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM);
	if (ret) {
345
		NV_ERROR(drm, "failed to pin fb: %d\n", ret);
346
		goto out_unref;
347 348 349 350
	}

	ret = nouveau_bo_map(nvbo);
	if (ret) {
351
		NV_ERROR(drm, "failed to map fb: %d\n", ret);
352
		goto out_unpin;
353 354
	}

355
	chan = nouveau_nofbaccel ? NULL : drm->channel;
356
	if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
357
		ret = nouveau_bo_vma_add(nvbo, drm->client.vm,
358
					&fbcon->nouveau_fb.vma);
359
		if (ret) {
360
			NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
361 362 363 364
			chan = NULL;
		}
	}

365 366
	mutex_lock(&dev->struct_mutex);

367
	info = framebuffer_alloc(0, &pdev->dev);
368 369
	if (!info) {
		ret = -ENOMEM;
370
		goto out_unlock;
371 372
	}

373 374
	ret = fb_alloc_cmap(&info->cmap, 256, 0);
	if (ret) {
375
		ret = -ENOMEM;
376 377
		framebuffer_release(info);
		goto out_unlock;
378 379
	}

380
	info->par = fbcon;
381

382
	nouveau_framebuffer_init(dev, &fbcon->nouveau_fb, &mode_cmd, nvbo);
383

384
	nouveau_fb = &fbcon->nouveau_fb;
385
	fb = &nouveau_fb->base;
386

387
	/* setup helper */
388 389
	fbcon->helper.fb = fb;
	fbcon->helper.fbdev = info;
390 391

	strcpy(info->fix.id, "nouveaufb");
392
	if (!chan)
393 394 395 396 397
		info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
	else
		info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
			      FBINFO_HWACCEL_FILLRECT |
			      FBINFO_HWACCEL_IMAGEBLIT;
398
	info->flags |= FBINFO_CAN_FORCE_OUTPUT;
399
	info->fbops = &nouveau_fbcon_sw_ops;
400 401
	info->fix.smem_start = nvbo->bo.mem.bus.base +
			       nvbo->bo.mem.bus.offset;
402 403 404 405 406
	info->fix.smem_len = size;

	info->screen_base = nvbo_kmap_obj_iovirtual(nouveau_fb->nvbo);
	info->screen_size = size;

407
	drm_fb_helper_fill_fix(info, fb->pitches[0], fb->depth);
408
	drm_fb_helper_fill_var(info, &fbcon->helper, sizes->fb_width, sizes->fb_height);
409

410
	/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
411

412 413
	mutex_unlock(&dev->struct_mutex);

414 415
	if (chan)
		nouveau_fbcon_accel_init(dev);
416
	nouveau_fbcon_zfill(dev, fbcon);
417 418

	/* To allow resizeing without swapping buffers */
419 420 421
	NV_INFO(drm, "allocated %dx%d fb: 0x%lx, bo %p\n",
		nouveau_fb->base.width, nouveau_fb->base.height,
		nvbo->bo.offset, nvbo);
422

423
	vga_switcheroo_client_fb_set(dev->pdev, info);
424 425
	return 0;

426
out_unlock:
427
	mutex_unlock(&dev->struct_mutex);
428 429
	if (chan)
		nouveau_bo_vma_del(nvbo, &fbcon->nouveau_fb.vma);
430
	nouveau_bo_unmap(nvbo);
431 432 433 434
out_unpin:
	nouveau_bo_unpin(nvbo);
out_unref:
	nouveau_bo_ref(NULL, &nvbo);
435 436 437 438
out:
	return ret;
}

439 440
void
nouveau_fbcon_output_poll_changed(struct drm_device *dev)
441
{
442
	struct nouveau_drm *drm = nouveau_drm(dev);
443 444
	if (drm->fbcon)
		drm_fb_helper_hotplug_event(&drm->fbcon->helper);
445 446
}

447
static int
448
nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
449
{
450
	struct nouveau_framebuffer *nouveau_fb = &fbcon->nouveau_fb;
451 452
	struct fb_info *info;

453 454
	if (fbcon->helper.fbdev) {
		info = fbcon->helper.fbdev;
455
		unregister_framebuffer(info);
456 457
		if (info->cmap.len)
			fb_dealloc_cmap(&info->cmap);
458 459
		framebuffer_release(info);
	}
460

461
	if (nouveau_fb->nvbo) {
462
		nouveau_bo_unmap(nouveau_fb->nvbo);
463
		nouveau_bo_vma_del(nouveau_fb->nvbo, &nouveau_fb->vma);
464
		nouveau_bo_unpin(nouveau_fb->nvbo);
465
		drm_gem_object_unreference_unlocked(&nouveau_fb->nvbo->gem);
466 467
		nouveau_fb->nvbo = NULL;
	}
468
	drm_fb_helper_fini(&fbcon->helper);
469
	drm_framebuffer_unregister_private(&nouveau_fb->base);
470
	drm_framebuffer_cleanup(&nouveau_fb->base);
471 472
	return 0;
}
473 474 475

void nouveau_fbcon_gpu_lockup(struct fb_info *info)
{
476
	struct nouveau_fbdev *fbcon = info->par;
477
	struct nouveau_drm *drm = nouveau_drm(fbcon->dev);
478

479
	NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
480 481
	info->flags |= FBINFO_HWACCEL_DISABLED;
}
482

483
static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
484 485
	.gamma_set = nouveau_fbcon_gamma_set,
	.gamma_get = nouveau_fbcon_gamma_get,
486
	.fb_probe = nouveau_fbcon_create,
487 488 489
};


490 491
int
nouveau_fbcon_init(struct drm_device *dev)
492
{
493
	struct nouveau_drm *drm = nouveau_drm(dev);
494
	struct nouveau_fb *pfb = nvkm_fb(&drm->device);
495
	struct nouveau_fbdev *fbcon;
496
	int preferred_bpp;
497
	int ret;
498

499 500
	if (!dev->mode_config.num_crtc ||
	    (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
501 502 503 504
		return 0;

	fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
	if (!fbcon)
505 506
		return -ENOMEM;

507 508
	fbcon->dev = dev;
	drm->fbcon = fbcon;
509 510

	drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
511

512
	ret = drm_fb_helper_init(dev, &fbcon->helper,
513
				 dev->mode_config.num_crtc, 4);
514
	if (ret) {
515
		kfree(fbcon);
516 517 518
		return ret;
	}

519
	drm_fb_helper_single_add_all_connectors(&fbcon->helper);
520

521
	if (pfb->ram->size <= 32 * 1024 * 1024)
522
		preferred_bpp = 8;
523
	else
524
	if (pfb->ram->size <= 64 * 1024 * 1024)
525 526 527 528
		preferred_bpp = 16;
	else
		preferred_bpp = 32;

529 530 531
	/* disable all the possible outputs/crtcs before entering KMS mode */
	drm_helper_disable_unused_functions(dev);

532
	drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
533 534 535
	return 0;
}

536 537
void
nouveau_fbcon_fini(struct drm_device *dev)
538
{
539
	struct nouveau_drm *drm = nouveau_drm(dev);
540

541
	if (!drm->fbcon)
542 543
		return;

544
	nouveau_fbcon_accel_fini(dev);
545 546 547
	nouveau_fbcon_destroy(dev, drm->fbcon);
	kfree(drm->fbcon);
	drm->fbcon = NULL;
548 549
}

550 551
void
nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
552
{
553
	struct nouveau_drm *drm = nouveau_drm(dev);
554 555
	if (drm->fbcon) {
		console_lock();
556
		if (state == 0) {
557
			nouveau_fbcon_accel_restore(dev);
558 559
			nouveau_fbcon_zfill(dev, drm->fbcon);
		}
560 561 562
		fb_set_suspend(drm->fbcon->helper.fbdev, state);
		if (state == 1)
			nouveau_fbcon_accel_save_disable(dev);
563 564
		console_unlock();
	}
565
}