aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/nouveau/nouveau_fbcon.c
diff options
context:
space:
mode:
authorBen Skeggs <bskeggs@redhat.com>2009-12-11 04:24:15 -0500
committerDave Airlie <airlied@redhat.com>2009-12-11 06:29:34 -0500
commit6ee738610f41b59733f63718f0bdbcba7d3a3f12 (patch)
treeeccb9f07671998c50a1bc606a54cd6f82ba43e0a /drivers/gpu/drm/nouveau/nouveau_fbcon.c
parentd1ede145cea25c5b6d2ebb19b167af14e374bb45 (diff)
drm/nouveau: Add DRM driver for NVIDIA GPUs
This adds a drm/kms staging non-API stable driver for GPUs from NVIDIA. This driver is a KMS-based driver and requires a compatible nouveau userspace libdrm and nouveau X.org driver. This driver requires firmware files not available in this kernel tree, interested parties can find them via the nouveau project git archive. This driver is reverse engineered, and is in no way supported by nVidia. Support for nearly the complete range of nvidia hw from nv04->g80 (nv50) is available, and the kms driver should support driving nearly all output types (displayport is under development still) along with supporting suspend/resume. This work is all from the upstream nouveau project found at nouveau.freedesktop.org. The original authors list from nouveau git tree is: Anssi Hannula <anssi.hannula@iki.fi> Ben Skeggs <bskeggs@redhat.com> Francisco Jerez <currojerez@riseup.net> Maarten Maathuis <madman2003@gmail.com> Marcin Kościelnicki <koriakin@0x04.net> Matthew Garrett <mjg@redhat.com> Matt Parnell <mparnell@gmail.com> Patrice Mandin <patmandin@gmail.com> Pekka Paalanen <pq@iki.fi> Xavier Chantry <shiningxc@gmail.com> along with project founder Stephane Marchesin <marchesin@icps.u-strasbg.fr> Signed-off-by: Ben Skeggs <bskeggs@redhat.com> Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/nouveau/nouveau_fbcon.c')
-rw-r--r--drivers/gpu/drm/nouveau/nouveau_fbcon.c380
1 files changed, 380 insertions, 0 deletions
diff --git a/drivers/gpu/drm/nouveau/nouveau_fbcon.c b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
new file mode 100644
index 000000000000..36e8c5e4503a
--- /dev/null
+++ b/drivers/gpu/drm/nouveau/nouveau_fbcon.c
@@ -0,0 +1,380 @@
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/slab.h>
34#include <linux/sysrq.h>
35#include <linux/delay.h>
36#include <linux/fb.h>
37#include <linux/init.h>
38#include <linux/screen_info.h>
39
40#include "drmP.h"
41#include "drm.h"
42#include "drm_crtc.h"
43#include "drm_crtc_helper.h"
44#include "drm_fb_helper.h"
45#include "nouveau_drv.h"
46#include "nouveau_drm.h"
47#include "nouveau_crtc.h"
48#include "nouveau_fb.h"
49#include "nouveau_fbcon.h"
50#include "nouveau_dma.h"
51
52static int
53nouveau_fbcon_sync(struct fb_info *info)
54{
55 struct nouveau_fbcon_par *par = info->par;
56 struct drm_device *dev = par->dev;
57 struct drm_nouveau_private *dev_priv = dev->dev_private;
58 struct nouveau_channel *chan = dev_priv->channel;
59 int ret, i;
60
61 if (!chan->accel_done ||
62 info->state != FBINFO_STATE_RUNNING ||
63 info->flags & FBINFO_HWACCEL_DISABLED)
64 return 0;
65
66 if (RING_SPACE(chan, 4)) {
67 NV_ERROR(dev, "GPU lockup - switching to software fbcon\n");
68 info->flags |= FBINFO_HWACCEL_DISABLED;
69 return 0;
70 }
71
72 BEGIN_RING(chan, 0, 0x0104, 1);
73 OUT_RING(chan, 0);
74 BEGIN_RING(chan, 0, 0x0100, 1);
75 OUT_RING(chan, 0);
76 nouveau_bo_wr32(chan->notifier_bo, chan->m2mf_ntfy + 3, 0xffffffff);
77 FIRE_RING(chan);
78
79 ret = -EBUSY;
80 for (i = 0; i < 100000; i++) {
81 if (!nouveau_bo_rd32(chan->notifier_bo, chan->m2mf_ntfy + 3)) {
82 ret = 0;
83 break;
84 }
85 DRM_UDELAY(1);
86 }
87
88 if (ret) {
89 NV_ERROR(dev, "GPU lockup - switching to software fbcon\n");
90 info->flags |= FBINFO_HWACCEL_DISABLED;
91 return 0;
92 }
93
94 chan->accel_done = false;
95 return 0;
96}
97
98static struct fb_ops nouveau_fbcon_ops = {
99 .owner = THIS_MODULE,
100 .fb_check_var = drm_fb_helper_check_var,
101 .fb_set_par = drm_fb_helper_set_par,
102 .fb_setcolreg = drm_fb_helper_setcolreg,
103 .fb_fillrect = cfb_fillrect,
104 .fb_copyarea = cfb_copyarea,
105 .fb_imageblit = cfb_imageblit,
106 .fb_sync = nouveau_fbcon_sync,
107 .fb_pan_display = drm_fb_helper_pan_display,
108 .fb_blank = drm_fb_helper_blank,
109 .fb_setcmap = drm_fb_helper_setcmap,
110};
111
112static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
113 u16 blue, int regno)
114{
115 struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
116
117 nv_crtc->lut.r[regno] = red;
118 nv_crtc->lut.g[regno] = green;
119 nv_crtc->lut.b[regno] = blue;
120}
121
122static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
123 u16 *blue, int regno)
124{
125 struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
126
127 *red = nv_crtc->lut.r[regno];
128 *green = nv_crtc->lut.g[regno];
129 *blue = nv_crtc->lut.b[regno];
130}
131
132static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
133 .gamma_set = nouveau_fbcon_gamma_set,
134 .gamma_get = nouveau_fbcon_gamma_get
135};
136
137#if defined(__i386__) || defined(__x86_64__)
138static bool
139nouveau_fbcon_has_vesafb_or_efifb(struct drm_device *dev)
140{
141 struct pci_dev *pdev = dev->pdev;
142 int ramin;
143
144 if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB &&
145 screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
146 return false;
147
148 if (screen_info.lfb_base < pci_resource_start(pdev, 1))
149 goto not_fb;
150
151 if (screen_info.lfb_base + screen_info.lfb_size >=
152 pci_resource_start(pdev, 1) + pci_resource_len(pdev, 1))
153 goto not_fb;
154
155 return true;
156not_fb:
157 ramin = 2;
158 if (pci_resource_len(pdev, ramin) == 0) {
159 ramin = 3;
160 if (pci_resource_len(pdev, ramin) == 0)
161 return false;
162 }
163
164 if (screen_info.lfb_base < pci_resource_start(pdev, ramin))
165 return false;
166
167 if (screen_info.lfb_base + screen_info.lfb_size >=
168 pci_resource_start(pdev, ramin) + pci_resource_len(pdev, ramin))
169 return false;
170
171 return true;
172}
173#endif
174
175void
176nouveau_fbcon_zfill(struct drm_device *dev)
177{
178 struct drm_nouveau_private *dev_priv = dev->dev_private;
179 struct fb_info *info = dev_priv->fbdev_info;
180 struct fb_fillrect rect;
181
182 /* Clear the entire fbcon. The drm will program every connector
183 * with it's preferred mode. If the sizes differ, one display will
184 * quite likely have garbage around the console.
185 */
186 rect.dx = rect.dy = 0;
187 rect.width = info->var.xres_virtual;
188 rect.height = info->var.yres_virtual;
189 rect.color = 0;
190 rect.rop = ROP_COPY;
191 info->fbops->fb_fillrect(info, &rect);
192}
193
194static int
195nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
196 uint32_t fb_height, uint32_t surface_width,
197 uint32_t surface_height, uint32_t surface_depth,
198 uint32_t surface_bpp, struct drm_framebuffer **pfb)
199{
200 struct drm_nouveau_private *dev_priv = dev->dev_private;
201 struct fb_info *info;
202 struct nouveau_fbcon_par *par;
203 struct drm_framebuffer *fb;
204 struct nouveau_framebuffer *nouveau_fb;
205 struct nouveau_bo *nvbo;
206 struct drm_mode_fb_cmd mode_cmd;
207 struct device *device = &dev->pdev->dev;
208 int size, ret;
209
210 mode_cmd.width = surface_width;
211 mode_cmd.height = surface_height;
212
213 mode_cmd.bpp = surface_bpp;
214 mode_cmd.pitch = mode_cmd.width * (mode_cmd.bpp >> 3);
215 mode_cmd.pitch = ALIGN(mode_cmd.pitch, 256);
216 mode_cmd.depth = surface_depth;
217
218 size = mode_cmd.pitch * mode_cmd.height;
219 size = ALIGN(size, PAGE_SIZE);
220
221 ret = nouveau_gem_new(dev, dev_priv->channel, size, 0, TTM_PL_FLAG_VRAM,
222 0, 0x0000, false, true, &nvbo);
223 if (ret) {
224 NV_ERROR(dev, "failed to allocate framebuffer\n");
225 goto out;
226 }
227
228 ret = nouveau_bo_pin(nvbo, TTM_PL_FLAG_VRAM);
229 if (ret) {
230 NV_ERROR(dev, "failed to pin fb: %d\n", ret);
231 nouveau_bo_ref(NULL, &nvbo);
232 goto out;
233 }
234
235 ret = nouveau_bo_map(nvbo);
236 if (ret) {
237 NV_ERROR(dev, "failed to map fb: %d\n", ret);
238 nouveau_bo_unpin(nvbo);
239 nouveau_bo_ref(NULL, &nvbo);
240 goto out;
241 }
242
243 mutex_lock(&dev->struct_mutex);
244
245 fb = nouveau_framebuffer_create(dev, nvbo, &mode_cmd);
246 if (!fb) {
247 ret = -ENOMEM;
248 NV_ERROR(dev, "failed to allocate fb.\n");
249 goto out_unref;
250 }
251
252 list_add(&fb->filp_head, &dev->mode_config.fb_kernel_list);
253
254 nouveau_fb = nouveau_framebuffer(fb);
255 *pfb = fb;
256
257 info = framebuffer_alloc(sizeof(struct nouveau_fbcon_par), device);
258 if (!info) {
259 ret = -ENOMEM;
260 goto out_unref;
261 }
262
263 par = info->par;
264 par->helper.funcs = &nouveau_fbcon_helper_funcs;
265 par->helper.dev = dev;
266 ret = drm_fb_helper_init_crtc_count(&par->helper, 2, 4);
267 if (ret)
268 goto out_unref;
269 dev_priv->fbdev_info = info;
270
271 strcpy(info->fix.id, "nouveaufb");
272 info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
273 FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_IMAGEBLIT;
274 info->fbops = &nouveau_fbcon_ops;
275 info->fix.smem_start = dev->mode_config.fb_base + nvbo->bo.offset -
276 dev_priv->vm_vram_base;
277 info->fix.smem_len = size;
278
279 info->screen_base = nvbo_kmap_obj_iovirtual(nouveau_fb->nvbo);
280 info->screen_size = size;
281
282 drm_fb_helper_fill_fix(info, fb->pitch, fb->depth);
283 drm_fb_helper_fill_var(info, fb, fb_width, fb_height);
284
285 /* FIXME: we really shouldn't expose mmio space at all */
286 info->fix.mmio_start = pci_resource_start(dev->pdev, 1);
287 info->fix.mmio_len = pci_resource_len(dev->pdev, 1);
288
289 /* Set aperture base/size for vesafb takeover */
290#if defined(__i386__) || defined(__x86_64__)
291 if (nouveau_fbcon_has_vesafb_or_efifb(dev)) {
292 /* Some NVIDIA VBIOS' are stupid and decide to put the
293 * framebuffer in the middle of the PRAMIN BAR for
294 * whatever reason. We need to know the exact lfb_base
295 * to get vesafb kicked off, and the only reliable way
296 * we have left is to find out lfb_base the same way
297 * vesafb did.
298 */
299 info->aperture_base = screen_info.lfb_base;
300 info->aperture_size = screen_info.lfb_size;
301 if (screen_info.orig_video_isVGA == VIDEO_TYPE_VLFB)
302 info->aperture_size *= 65536;
303 } else
304#endif
305 {
306 info->aperture_base = info->fix.mmio_start;
307 info->aperture_size = info->fix.mmio_len;
308 }
309
310 info->pixmap.size = 64*1024;
311 info->pixmap.buf_align = 8;
312 info->pixmap.access_align = 32;
313 info->pixmap.flags = FB_PIXMAP_SYSTEM;
314 info->pixmap.scan_align = 1;
315
316 fb->fbdev = info;
317
318 par->nouveau_fb = nouveau_fb;
319 par->dev = dev;
320
321 switch (dev_priv->card_type) {
322 case NV_50:
323 nv50_fbcon_accel_init(info);
324 break;
325 default:
326 nv04_fbcon_accel_init(info);
327 break;
328 };
329
330 nouveau_fbcon_zfill(dev);
331
332 /* To allow resizeing without swapping buffers */
333 NV_INFO(dev, "allocated %dx%d fb: 0x%lx, bo %p\n",
334 nouveau_fb->base.width,
335 nouveau_fb->base.height,
336 nvbo->bo.offset, nvbo);
337
338 mutex_unlock(&dev->struct_mutex);
339 return 0;
340
341out_unref:
342 mutex_unlock(&dev->struct_mutex);
343out:
344 return ret;
345}
346
347int
348nouveau_fbcon_probe(struct drm_device *dev)
349{
350 NV_DEBUG(dev, "\n");
351
352 return drm_fb_helper_single_fb_probe(dev, 32, nouveau_fbcon_create);
353}
354
355int
356nouveau_fbcon_remove(struct drm_device *dev, struct drm_framebuffer *fb)
357{
358 struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fb);
359 struct fb_info *info;
360
361 if (!fb)
362 return -EINVAL;
363
364 info = fb->fbdev;
365 if (info) {
366 struct nouveau_fbcon_par *par = info->par;
367
368 unregister_framebuffer(info);
369 nouveau_bo_unmap(nouveau_fb->nvbo);
370 mutex_lock(&dev->struct_mutex);
371 drm_gem_object_unreference(nouveau_fb->nvbo->gem);
372 nouveau_fb->nvbo = NULL;
373 mutex_unlock(&dev->struct_mutex);
374 if (par)
375 drm_fb_helper_free(&par->helper);
376 framebuffer_release(info);
377 }
378
379 return 0;
380}