2 * Copyright © 2007 David Airlie
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:
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
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.
28 #include <linux/module.h>
29 #include <linux/kernel.h>
30 #include <linux/errno.h>
31 #include <linux/string.h>
33 #include <linux/tty.h>
34 #include <linux/sysrq.h>
35 #include <linux/delay.h>
37 #include <linux/init.h>
38 #include <linux/screen_info.h>
39 #include <linux/vga_switcheroo.h>
42 #include "nouveau_drv.h"
45 #include "drm_crtc_helper.h"
46 #include "drm_fb_helper.h"
47 #include "nouveau_crtc.h"
48 #include "nouveau_fb.h"
49 #include "nouveau_fbcon.h"
50 #include "nouveau_dma.h"
51 #include "pscnv_gem.h"
53 #include "pscnv_kapi.h"
58 nouveau_fbcon_sync(struct fb_info
*info
)
61 struct nouveau_fbdev
*nfbdev
= info
->par
;
62 struct drm_device
*dev
= nfbdev
->dev
;
63 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
64 struct nouveau_channel
*chan
= dev_priv
->channel
;
67 if (!chan
|| !chan
->accel_done
|| in_interrupt() ||
68 info
->state
!= FBINFO_STATE_RUNNING
||
69 info
->flags
& FBINFO_HWACCEL_DISABLED
)
72 if (!mutex_trylock(&chan
->mutex
))
75 ret
= RING_SPACE(chan
, 4);
77 mutex_unlock(&chan
->mutex
);
78 nouveau_fbcon_gpu_lockup(info
);
82 BEGIN_RING(chan
, 0, 0x0104, 1);
84 BEGIN_RING(chan
, 0, 0x0100, 1);
86 nouveau_bo_wr32(chan
->notifier_bo
, chan
->m2mf_ntfy
+ 3, 0xffffffff);
88 mutex_unlock(&chan
->mutex
);
91 for (i
= 0; i
< 100000; i
++) {
92 if (!nouveau_bo_rd32(chan
->notifier_bo
, chan
->m2mf_ntfy
+ 3)) {
100 nouveau_fbcon_gpu_lockup(info
);
104 chan
->accel_done
= false;
109 static struct fb_ops nouveau_fbcon_ops
= {
110 .owner
= THIS_MODULE
,
111 .fb_check_var
= drm_fb_helper_check_var
,
112 .fb_set_par
= drm_fb_helper_set_par
,
113 .fb_fillrect
= cfb_fillrect
,
114 .fb_copyarea
= cfb_copyarea
,
115 .fb_imageblit
= cfb_imageblit
,
116 .fb_sync
= nouveau_fbcon_sync
,
117 .fb_pan_display
= drm_fb_helper_pan_display
,
118 .fb_blank
= drm_fb_helper_blank
,
119 .fb_setcmap
= drm_fb_helper_setcmap
,
122 static struct fb_ops nv04_fbcon_ops
= {
123 .owner
= THIS_MODULE
,
124 .fb_check_var
= drm_fb_helper_check_var
,
125 .fb_set_par
= drm_fb_helper_set_par
,
126 .fb_fillrect
= nv04_fbcon_fillrect
,
127 .fb_copyarea
= nv04_fbcon_copyarea
,
128 .fb_imageblit
= nv04_fbcon_imageblit
,
129 .fb_sync
= nouveau_fbcon_sync
,
130 .fb_pan_display
= drm_fb_helper_pan_display
,
131 .fb_blank
= drm_fb_helper_blank
,
132 .fb_setcmap
= drm_fb_helper_setcmap
,
135 static struct fb_ops nv50_fbcon_ops
= {
136 .owner
= THIS_MODULE
,
137 .fb_check_var
= drm_fb_helper_check_var
,
138 .fb_set_par
= drm_fb_helper_set_par
,
139 .fb_fillrect
= nv50_fbcon_fillrect
,
140 .fb_copyarea
= nv50_fbcon_copyarea
,
141 .fb_imageblit
= nv50_fbcon_imageblit
,
142 .fb_sync
= nouveau_fbcon_sync
,
143 .fb_pan_display
= drm_fb_helper_pan_display
,
144 .fb_blank
= drm_fb_helper_blank
,
145 .fb_setcmap
= drm_fb_helper_setcmap
,
151 static void nouveau_fbcon_gamma_set(struct drm_crtc
*crtc
, u16 red
, u16 green
,
154 struct nouveau_crtc
*nv_crtc
= nouveau_crtc(crtc
);
156 nv_crtc
->lut
.r
[regno
] = red
;
157 nv_crtc
->lut
.g
[regno
] = green
;
158 nv_crtc
->lut
.b
[regno
] = blue
;
161 static void nouveau_fbcon_gamma_get(struct drm_crtc
*crtc
, u16
*red
, u16
*green
,
162 u16
*blue
, int regno
)
164 struct nouveau_crtc
*nv_crtc
= nouveau_crtc(crtc
);
166 *red
= nv_crtc
->lut
.r
[regno
];
167 *green
= nv_crtc
->lut
.g
[regno
];
168 *blue
= nv_crtc
->lut
.b
[regno
];
172 nouveau_fbcon_zfill(struct drm_device
*dev
, struct nouveau_fbdev
*nfbdev
)
175 struct fb_info
*info
= nfbdev
->helper
.fbdev
;
176 struct fb_fillrect rect
;
178 /* Clear the entire fbcon. The drm will program every connector
179 * with it's preferred mode. If the sizes differ, one display will
180 * quite likely have garbage around the console.
182 rect
.dx
= rect
.dy
= 0;
183 rect
.width
= info
->var
.xres_virtual
;
184 rect
.height
= info
->var
.yres_virtual
;
187 info
->fbops
->fb_fillrect(info
, &rect
);
189 WARN(1, "Missing!\n");
194 nouveau_fbcon_create(struct nouveau_fbdev
*nfbdev
,
195 struct drm_fb_helper_surface_size
*sizes
)
197 struct drm_device
*dev
= nfbdev
->dev
;
198 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
200 struct fb_info
*info
;
202 struct drm_framebuffer
*fb
;
203 struct nouveau_framebuffer
*nouveau_fb
;
204 struct pscnv_bo
*nvbo
;
205 struct drm_gem_object
*obj
;
206 #ifdef PSCNV_KAPI_DRM_MODE_FB_CMD2
207 struct drm_mode_fb_cmd2 mode_cmd
;
209 struct drm_mode_fb_cmd mode_cmd
;
213 mode_cmd
.width
= sizes
->surface_width
;
214 mode_cmd
.height
= sizes
->surface_height
;
216 #ifdef PSCNV_KAPI_DRM_MODE_FB_CMD2
217 mode_cmd
.pitches
[0] = mode_cmd
.width
* (sizes
->surface_bpp
>> 3);
218 mode_cmd
.pitches
[0] = roundup(mode_cmd
.pitches
[0], 256);
219 mode_cmd
.pixel_format
= drm_mode_legacy_fb_format(sizes
->surface_bpp
,
220 sizes
->surface_depth
);
222 mode_cmd
.bpp
= sizes
->surface_bpp
;
223 mode_cmd
.pitch
= mode_cmd
.width
* (mode_cmd
.bpp
>> 3);
224 mode_cmd
.pitch
= roundup(mode_cmd
.pitch
, 256);
225 mode_cmd
.depth
= sizes
->surface_depth
;
228 #ifdef PSCNV_KAPI_DRM_MODE_FB_CMD2
229 size
= mode_cmd
.pitches
[0] * mode_cmd
.height
;
231 size
= mode_cmd
.pitch
* mode_cmd
.height
;
233 size
= roundup(size
, PAGE_SIZE
);
235 obj
= pscnv_gem_new(dev
, size
, PSCNV_GEM_CONTIG
, 0, 0xd15fb, 0);
238 NV_ERROR(dev
, "failed to allocate framebuffer\n");
241 nvbo
= obj
->driver_private
;
243 ret
= dev_priv
->vm
->map_user(nvbo
);
245 NV_ERROR(dev
, "failed to map fb: %d\n", ret
);
246 pscnv_mem_free(nvbo
);
251 mutex_lock(&dev
->struct_mutex
);
253 info
= framebuffer_alloc(0, &dev
->pdev
->dev
);
259 ret
= fb_alloc_cmap(&info
->cmap
, 256, 0);
270 nouveau_framebuffer_init(dev
, &nfbdev
->nouveau_fb
, &mode_cmd
, nvbo
);
272 nouveau_fb
= &nfbdev
->nouveau_fb
;
273 fb
= &nouveau_fb
->base
;
276 nfbdev
->helper
.fb
= fb
;
278 nfbdev
->helper
.fbdev
= info
;
280 strcpy(info
->fix
.id
, "nouveaufb");
281 if (nouveau_nofbaccel
)
282 info
->flags
= FBINFO_DEFAULT
| FBINFO_HWACCEL_DISABLED
;
284 info
->flags
= FBINFO_DEFAULT
| FBINFO_HWACCEL_COPYAREA
|
285 FBINFO_HWACCEL_FILLRECT
|
286 FBINFO_HWACCEL_IMAGEBLIT
;
287 #ifdef FBINFO_CAN_FORCE_OUTPUT
288 info
->flags
|= FBINFO_CAN_FORCE_OUTPUT
;
290 info
->fbops
= &nouveau_fbcon_ops
;
291 info
->fix
.smem_start
= dev
->mode_config
.fb_base
+ nvbo
->map1
->start
;
292 info
->fix
.smem_len
= size
;
294 info
->screen_base
= ioremap_wc(dev_priv
->fb_phys
+ nvbo
->map1
->start
, size
);
295 info
->screen_size
= size
;
297 #ifdef PSCNV_KAPI_DRM_FB_PITCH
298 drm_fb_helper_fill_fix(info
, fb
->pitch
, fb
->depth
);
300 drm_fb_helper_fill_fix(info
, fb
->pitches
[0], fb
->depth
);
302 drm_fb_helper_fill_var(info
, &nfbdev
->helper
, sizes
->fb_width
, sizes
->fb_height
);
304 /* FIXME: we really shouldn't expose mmio space at all */
305 info
->fix
.mmio_start
= drm_get_resource_start(dev
, 1);
306 info
->fix
.mmio_len
= drm_get_resource_len(dev
, 1);
308 #ifndef PSCNV_KAPI_FB_INFO_APERTURES
309 /* Set aperture base/size for vesafb takeover */
310 info
->apertures
= dev_priv
->apertures
;
311 if (!info
->apertures
) {
317 info
->pixmap
.size
= 64*1024;
318 info
->pixmap
.buf_align
= 8;
319 info
->pixmap
.access_align
= 32;
320 info
->pixmap
.flags
= FB_PIXMAP_SYSTEM
;
321 info
->pixmap
.scan_align
= 1;
323 mutex_unlock(&dev
->struct_mutex
);
328 if (dev_priv
->channel
&& !nouveau_nofbaccel
) {
329 switch (dev_priv
->card_type
) {
331 nv50_fbcon_accel_init(info
);
332 info
->fbops
= &nv50_fbcon_ops
;
335 nv04_fbcon_accel_init(info
);
336 info
->fbops
= &nv04_fbcon_ops
;
341 nouveau_fbcon_zfill(dev
, nfbdev
);
343 /* To allow resizeing without swapping buffers */
344 NV_INFO(dev
, "allocated %dx%d fb: 0x%llx 0x%llx, bo %p\n",
345 nouveau_fb
->base
.width
,
346 nouveau_fb
->base
.height
,
347 nvbo
->start
, nvbo
->map1
->start
, nvbo
);
350 vga_switcheroo_client_fb_set(dev
->pdev
, info
);
356 mutex_unlock(&dev
->struct_mutex
);
363 nouveau_fbcon_find_or_create_single(struct drm_fb_helper
*helper
,
364 struct drm_fb_helper_surface_size
*sizes
)
366 struct nouveau_fbdev
*nfbdev
= (struct nouveau_fbdev
*)helper
;
371 ret
= nouveau_fbcon_create(nfbdev
, sizes
);
380 nouveau_fbcon_output_poll_changed(struct drm_device
*dev
)
382 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
383 drm_fb_helper_hotplug_event(&dev_priv
->nfbdev
->helper
);
387 nouveau_fbcon_destroy(struct drm_device
*dev
, struct nouveau_fbdev
*nfbdev
)
389 struct nouveau_framebuffer
*nouveau_fb
= &nfbdev
->nouveau_fb
;
392 if (nfbdev
->helper
.fbdev
) {
393 struct fb_info
*info
= nfbdev
->helper
.fbdev
;
394 unregister_framebuffer(info
);
396 fb_dealloc_cmap(&info
->cmap
);
397 framebuffer_release(info
);
401 if (nouveau_fb
->nvbo
) {
402 drm_gem_object_unreference_unlocked(nouveau_fb
->nvbo
->gem
);
403 nouveau_fb
->nvbo
= NULL
;
405 drm_fb_helper_fini(&nfbdev
->helper
);
406 drm_framebuffer_cleanup(&nouveau_fb
->base
);
411 void nouveau_fbcon_gpu_lockup(struct fb_info
*info
)
413 struct nouveau_fbdev
*nfbdev
= info
->par
;
414 struct drm_device
*dev
= nfbdev
->dev
;
416 NV_ERROR(dev
, "GPU lockup - switching to software fbcon\n");
417 info
->flags
|= FBINFO_HWACCEL_DISABLED
;
421 static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs
= {
422 .gamma_set
= nouveau_fbcon_gamma_set
,
423 .gamma_get
= nouveau_fbcon_gamma_get
,
424 .fb_probe
= nouveau_fbcon_find_or_create_single
,
428 int nouveau_fbcon_init(struct drm_device
*dev
)
430 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
431 struct nouveau_fbdev
*nfbdev
;
434 nfbdev
= kzalloc(sizeof(struct nouveau_fbdev
), GFP_KERNEL
);
439 dev_priv
->nfbdev
= nfbdev
;
440 nfbdev
->helper
.funcs
= &nouveau_fbcon_helper_funcs
;
442 ret
= drm_fb_helper_init(dev
, &nfbdev
->helper
,
443 nv_two_heads(dev
) ? 2 : 1, 4);
449 drm_fb_helper_single_add_all_connectors(&nfbdev
->helper
);
450 drm_fb_helper_initial_config(&nfbdev
->helper
, 32);
454 void nouveau_fbcon_fini(struct drm_device
*dev
)
456 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
458 if (!dev_priv
->nfbdev
)
461 nouveau_fbcon_destroy(dev
, dev_priv
->nfbdev
);
462 kfree(dev_priv
->nfbdev
);
463 dev_priv
->nfbdev
= NULL
;
467 void nouveau_fbcon_save_disable_accel(struct drm_device
*dev
)
469 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
471 dev_priv
->nfbdev
->saved_flags
= dev_priv
->nfbdev
->helper
.fbdev
->flags
;
472 dev_priv
->nfbdev
->helper
.fbdev
->flags
|= FBINFO_HWACCEL_DISABLED
;
475 void nouveau_fbcon_restore_accel(struct drm_device
*dev
)
477 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
478 dev_priv
->nfbdev
->helper
.fbdev
->flags
= dev_priv
->nfbdev
->saved_flags
;
481 void nouveau_fbcon_set_suspend(struct drm_device
*dev
, int state
)
483 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
484 fb_set_suspend(dev_priv
->nfbdev
->helper
.fbdev
, state
);
488 void nouveau_fbcon_zfill_all(struct drm_device
*dev
)
490 struct drm_nouveau_private
*dev_priv
= dev
->dev_private
;
491 nouveau_fbcon_zfill(dev
, dev_priv
->nfbdev
);