Home | History | Annotate | Line # | Download | only in nouveau
nouveau_fbcon.c revision 1.1.1.1
      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 
     59 MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
     60 static int nouveau_nofbaccel = 0;
     61 module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
     62 
     63 static void
     64 nouveau_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 
     95 static void
     96 nouveau_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 
    127 static void
    128 nouveau_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 
    159 static int
    160 nouveau_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 
    186 static 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 
    201 static 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 
    215 static 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 
    225 static 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 
    235 static void
    236 nouveau_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 
    253 static int
    254 nouveau_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 
    384 out_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);
    389 out_unpin:
    390 	nouveau_bo_unpin(nvbo);
    391 out_unref:
    392 	nouveau_bo_ref(NULL, &nvbo);
    393 out:
    394 	return ret;
    395 }
    396 
    397 void
    398 nouveau_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 
    405 static int
    406 nouveau_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 
    432 void 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 
    441 static 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 
    448 int
    449 nouveau_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 	    (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
    459 		return 0;
    460 
    461 	fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
    462 	if (!fbcon)
    463 		return -ENOMEM;
    464 
    465 	fbcon->dev = dev;
    466 	drm->fbcon = fbcon;
    467 	fbcon->helper.funcs = &nouveau_fbcon_helper_funcs;
    468 
    469 	ret = drm_fb_helper_init(dev, &fbcon->helper,
    470 				 dev->mode_config.num_crtc, 4);
    471 	if (ret) {
    472 		kfree(fbcon);
    473 		return ret;
    474 	}
    475 
    476 	drm_fb_helper_single_add_all_connectors(&fbcon->helper);
    477 
    478 	if (pfb->ram->size <= 32 * 1024 * 1024)
    479 		preferred_bpp = 8;
    480 	else
    481 	if (pfb->ram->size <= 64 * 1024 * 1024)
    482 		preferred_bpp = 16;
    483 	else
    484 		preferred_bpp = 32;
    485 
    486 	/* disable all the possible outputs/crtcs before entering KMS mode */
    487 	drm_helper_disable_unused_functions(dev);
    488 
    489 	drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
    490 	return 0;
    491 }
    492 
    493 void
    494 nouveau_fbcon_fini(struct drm_device *dev)
    495 {
    496 	struct nouveau_drm *drm = nouveau_drm(dev);
    497 
    498 	if (!drm->fbcon)
    499 		return;
    500 
    501 	nouveau_fbcon_destroy(dev, drm->fbcon);
    502 	kfree(drm->fbcon);
    503 	drm->fbcon = NULL;
    504 }
    505 
    506 void
    507 nouveau_fbcon_save_disable_accel(struct drm_device *dev)
    508 {
    509 	struct nouveau_drm *drm = nouveau_drm(dev);
    510 	if (drm->fbcon) {
    511 		drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
    512 		drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
    513 	}
    514 }
    515 
    516 void
    517 nouveau_fbcon_restore_accel(struct drm_device *dev)
    518 {
    519 	struct nouveau_drm *drm = nouveau_drm(dev);
    520 	if (drm->fbcon) {
    521 		drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
    522 	}
    523 }
    524 
    525 void
    526 nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
    527 {
    528 	struct nouveau_drm *drm = nouveau_drm(dev);
    529 	if (drm->fbcon) {
    530 		console_lock();
    531 		if (state == 1)
    532 			nouveau_fbcon_save_disable_accel(dev);
    533 		fb_set_suspend(drm->fbcon->helper.fbdev, state);
    534 		if (state == 0)
    535 			nouveau_fbcon_restore_accel(dev);
    536 		console_unlock();
    537 	}
    538 }
    539 
    540 void
    541 nouveau_fbcon_zfill_all(struct drm_device *dev)
    542 {
    543 	struct nouveau_drm *drm = nouveau_drm(dev);
    544 	if (drm->fbcon) {
    545 		nouveau_fbcon_zfill(dev, drm->fbcon);
    546 	}
    547 }
    548