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