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