drm: Propagate error from drm_fb_helper_init().
[safe/jmp/linux-2.6] / drivers / gpu / drm / nouveau / nouveau_fbcon.c
index eddadac..c9a4a0d 100644 (file)
 #include <linux/string.h>
 #include <linux/mm.h>
 #include <linux/tty.h>
-#include <linux/slab.h>
 #include <linux/sysrq.h>
 #include <linux/delay.h>
 #include <linux/fb.h>
 #include <linux/init.h>
 #include <linux/screen_info.h>
+#include <linux/vga_switcheroo.h>
 
 #include "drmP.h"
 #include "drm.h"
@@ -52,8 +52,8 @@
 static int
 nouveau_fbcon_sync(struct fb_info *info)
 {
-       struct nouveau_fbcon_par *par = info->par;
-       struct drm_device *dev = par->dev;
+       struct nouveau_fbdev *nfbdev = info->par;
+       struct drm_device *dev = nfbdev->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct nouveau_channel *chan = dev_priv->channel;
        int ret, i;
@@ -97,7 +97,6 @@ static struct fb_ops nouveau_fbcon_ops = {
        .owner = THIS_MODULE,
        .fb_check_var = drm_fb_helper_check_var,
        .fb_set_par = drm_fb_helper_set_par,
-       .fb_setcolreg = drm_fb_helper_setcolreg,
        .fb_fillrect = cfb_fillrect,
        .fb_copyarea = cfb_copyarea,
        .fb_imageblit = cfb_imageblit,
@@ -107,6 +106,32 @@ static struct fb_ops nouveau_fbcon_ops = {
        .fb_setcmap = drm_fb_helper_setcmap,
 };
 
+static struct fb_ops nv04_fbcon_ops = {
+       .owner = THIS_MODULE,
+       .fb_check_var = drm_fb_helper_check_var,
+       .fb_set_par = drm_fb_helper_set_par,
+       .fb_fillrect = nv04_fbcon_fillrect,
+       .fb_copyarea = nv04_fbcon_copyarea,
+       .fb_imageblit = nv04_fbcon_imageblit,
+       .fb_sync = nouveau_fbcon_sync,
+       .fb_pan_display = drm_fb_helper_pan_display,
+       .fb_blank = drm_fb_helper_blank,
+       .fb_setcmap = drm_fb_helper_setcmap,
+};
+
+static struct fb_ops nv50_fbcon_ops = {
+       .owner = THIS_MODULE,
+       .fb_check_var = drm_fb_helper_check_var,
+       .fb_set_par = drm_fb_helper_set_par,
+       .fb_fillrect = nv50_fbcon_fillrect,
+       .fb_copyarea = nv50_fbcon_copyarea,
+       .fb_imageblit = nv50_fbcon_imageblit,
+       .fb_sync = nouveau_fbcon_sync,
+       .fb_pan_display = drm_fb_helper_pan_display,
+       .fb_blank = drm_fb_helper_blank,
+       .fb_setcmap = drm_fb_helper_setcmap,
+};
+
 static void nouveau_fbcon_gamma_set(struct drm_crtc *crtc, u16 red, u16 green,
                                    u16 blue, int regno)
 {
@@ -127,54 +152,10 @@ static void nouveau_fbcon_gamma_get(struct drm_crtc *crtc, u16 *red, u16 *green,
        *blue = nv_crtc->lut.b[regno];
 }
 
-static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
-       .gamma_set = nouveau_fbcon_gamma_set,
-       .gamma_get = nouveau_fbcon_gamma_get
-};
-
-#if defined(__i386__) || defined(__x86_64__)
-static bool
-nouveau_fbcon_has_vesafb_or_efifb(struct drm_device *dev)
-{
-       struct pci_dev *pdev = dev->pdev;
-       int ramin;
-
-       if (screen_info.orig_video_isVGA != VIDEO_TYPE_VLFB &&
-           screen_info.orig_video_isVGA != VIDEO_TYPE_EFI)
-               return false;
-
-       if (screen_info.lfb_base < pci_resource_start(pdev, 1))
-               goto not_fb;
-
-       if (screen_info.lfb_base + screen_info.lfb_size >=
-           pci_resource_start(pdev, 1) + pci_resource_len(pdev, 1))
-               goto not_fb;
-
-       return true;
-not_fb:
-       ramin = 2;
-       if (pci_resource_len(pdev, ramin) == 0) {
-               ramin = 3;
-               if (pci_resource_len(pdev, ramin) == 0)
-                       return false;
-       }
-
-       if (screen_info.lfb_base < pci_resource_start(pdev, ramin))
-               return false;
-
-       if (screen_info.lfb_base + screen_info.lfb_size >=
-           pci_resource_start(pdev, ramin) + pci_resource_len(pdev, ramin))
-               return false;
-
-       return true;
-}
-#endif
-
-void
-nouveau_fbcon_zfill(struct drm_device *dev)
+static void
+nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *nfbdev)
 {
-       struct drm_nouveau_private *dev_priv = dev->dev_private;
-       struct fb_info *info = dev_priv->fbdev_info;
+       struct fb_info *info = nfbdev->helper.fbdev;
        struct fb_fillrect rect;
 
        /* Clear the entire fbcon.  The drm will program every connector
@@ -190,28 +171,27 @@ nouveau_fbcon_zfill(struct drm_device *dev)
 }
 
 static int
-nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
-                    uint32_t fb_height, uint32_t surface_width,
-                    uint32_t surface_height, uint32_t surface_depth,
-                    uint32_t surface_bpp, struct drm_framebuffer **pfb)
+nouveau_fbcon_create(struct nouveau_fbdev *nfbdev,
+                    struct drm_fb_helper_surface_size *sizes)
 {
+       struct drm_device *dev = nfbdev->dev;
        struct drm_nouveau_private *dev_priv = dev->dev_private;
        struct fb_info *info;
-       struct nouveau_fbcon_par *par;
        struct drm_framebuffer *fb;
        struct nouveau_framebuffer *nouveau_fb;
        struct nouveau_bo *nvbo;
        struct drm_mode_fb_cmd mode_cmd;
-       struct device *device = &dev->pdev->dev;
+       struct pci_dev *pdev = dev->pdev;
+       struct device *device = &pdev->dev;
        int size, ret;
 
-       mode_cmd.width = surface_width;
-       mode_cmd.height = surface_height;
+       mode_cmd.width = sizes->surface_width;
+       mode_cmd.height = sizes->surface_height;
 
-       mode_cmd.bpp = surface_bpp;
+       mode_cmd.bpp = sizes->surface_bpp;
        mode_cmd.pitch = mode_cmd.width * (mode_cmd.bpp >> 3);
        mode_cmd.pitch = roundup(mode_cmd.pitch, 256);
-       mode_cmd.depth = surface_depth;
+       mode_cmd.depth = sizes->surface_depth;
 
        size = mode_cmd.pitch * mode_cmd.height;
        size = roundup(size, PAGE_SIZE);
@@ -240,31 +220,28 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
 
        mutex_lock(&dev->struct_mutex);
 
-       fb = nouveau_framebuffer_create(dev, nvbo, &mode_cmd);
-       if (!fb) {
+       info = framebuffer_alloc(0, device);
+       if (!info) {
                ret = -ENOMEM;
-               NV_ERROR(dev, "failed to allocate fb.\n");
                goto out_unref;
        }
 
-       list_add(&fb->filp_head, &dev->mode_config.fb_kernel_list);
-
-       nouveau_fb = nouveau_framebuffer(fb);
-       *pfb = fb;
-
-       info = framebuffer_alloc(sizeof(struct nouveau_fbcon_par), device);
-       if (!info) {
+       ret = fb_alloc_cmap(&info->cmap, 256, 0);
+       if (ret) {
                ret = -ENOMEM;
                goto out_unref;
        }
 
-       par = info->par;
-       par->helper.funcs = &nouveau_fbcon_helper_funcs;
-       par->helper.dev = dev;
-       ret = drm_fb_helper_init_crtc_count(&par->helper, 2, 4);
-       if (ret)
-               goto out_unref;
-       dev_priv->fbdev_info = info;
+       info->par = nfbdev;
+
+       nouveau_framebuffer_init(dev, &nfbdev->nouveau_fb, &mode_cmd, nvbo);
+
+       nouveau_fb = &nfbdev->nouveau_fb;
+       fb = &nouveau_fb->base;
+
+       /* setup helper */
+       nfbdev->helper.fb = fb;
+       nfbdev->helper.fbdev = info;
 
        strcpy(info->fix.id, "nouveaufb");
        if (nouveau_nofbaccel)
@@ -282,31 +259,17 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
        info->screen_size = size;
 
        drm_fb_helper_fill_fix(info, fb->pitch, fb->depth);
-       drm_fb_helper_fill_var(info, fb, fb_width, fb_height);
+       drm_fb_helper_fill_var(info, &nfbdev->helper, sizes->fb_width, sizes->fb_height);
 
        /* FIXME: we really shouldn't expose mmio space at all */
-       info->fix.mmio_start = pci_resource_start(dev->pdev, 1);
-       info->fix.mmio_len = pci_resource_len(dev->pdev, 1);
+       info->fix.mmio_start = pci_resource_start(pdev, 1);
+       info->fix.mmio_len = pci_resource_len(pdev, 1);
 
        /* Set aperture base/size for vesafb takeover */
-#if defined(__i386__) || defined(__x86_64__)
-       if (nouveau_fbcon_has_vesafb_or_efifb(dev)) {
-               /* Some NVIDIA VBIOS' are stupid and decide to put the
-                * framebuffer in the middle of the PRAMIN BAR for
-                * whatever reason.  We need to know the exact lfb_base
-                * to get vesafb kicked off, and the only reliable way
-                * we have left is to find out lfb_base the same way
-                * vesafb did.
-                */
-               info->aperture_base = screen_info.lfb_base;
-               info->aperture_size = screen_info.lfb_size;
-               if (screen_info.orig_video_isVGA == VIDEO_TYPE_VLFB)
-                       info->aperture_size *= 65536;
-       } else
-#endif
-       {
-               info->aperture_base = info->fix.mmio_start;
-               info->aperture_size = info->fix.mmio_len;
+       info->apertures = dev_priv->apertures;
+       if (!info->apertures) {
+               ret = -ENOMEM;
+               goto out_unref;
        }
 
        info->pixmap.size = 64*1024;
@@ -315,23 +278,20 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
        info->pixmap.flags = FB_PIXMAP_SYSTEM;
        info->pixmap.scan_align = 1;
 
-       fb->fbdev = info;
-
-       par->nouveau_fb = nouveau_fb;
-       par->dev = dev;
-
        if (dev_priv->channel && !nouveau_nofbaccel) {
                switch (dev_priv->card_type) {
                case NV_50:
                        nv50_fbcon_accel_init(info);
+                       info->fbops = &nv50_fbcon_ops;
                        break;
                default:
                        nv04_fbcon_accel_init(info);
+                       info->fbops = &nv04_fbcon_ops;
                        break;
                };
        }
 
-       nouveau_fbcon_zfill(dev);
+       nouveau_fbcon_zfill(dev, nfbdev);
 
        /* To allow resizeing without swapping buffers */
        NV_INFO(dev, "allocated %dx%d fb: 0x%lx, bo %p\n",
@@ -340,6 +300,7 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
                                                nvbo->bo.offset, nvbo);
 
        mutex_unlock(&dev->struct_mutex);
+       vga_switcheroo_client_fb_set(dev->pdev, info);
        return 0;
 
 out_unref:
@@ -348,46 +309,129 @@ out:
        return ret;
 }
 
-int
-nouveau_fbcon_probe(struct drm_device *dev)
+static int
+nouveau_fbcon_find_or_create_single(struct drm_fb_helper *helper,
+                                   struct drm_fb_helper_surface_size *sizes)
 {
-       NV_DEBUG_KMS(dev, "\n");
+       struct nouveau_fbdev *nfbdev = (struct nouveau_fbdev *)helper;
+       int new_fb = 0;
+       int ret;
+
+       if (!helper->fb) {
+               ret = nouveau_fbcon_create(nfbdev, sizes);
+               if (ret)
+                       return ret;
+               new_fb = 1;
+       }
+       return new_fb;
+}
 
-       return drm_fb_helper_single_fb_probe(dev, 32, nouveau_fbcon_create);
+void
+nouveau_fbcon_output_poll_changed(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       drm_fb_helper_hotplug_event(&dev_priv->nfbdev->helper);
 }
 
 int
-nouveau_fbcon_remove(struct drm_device *dev, struct drm_framebuffer *fb)
+nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *nfbdev)
 {
-       struct nouveau_framebuffer *nouveau_fb = nouveau_framebuffer(fb);
+       struct nouveau_framebuffer *nouveau_fb = &nfbdev->nouveau_fb;
        struct fb_info *info;
 
-       if (!fb)
-               return -EINVAL;
-
-       info = fb->fbdev;
-       if (info) {
-               struct nouveau_fbcon_par *par = info->par;
-
+       if (nfbdev->helper.fbdev) {
+               info = nfbdev->helper.fbdev;
                unregister_framebuffer(info);
-               nouveau_bo_unmap(nouveau_fb->nvbo);
-               mutex_lock(&dev->struct_mutex);
-               drm_gem_object_unreference(nouveau_fb->nvbo->gem);
-               nouveau_fb->nvbo = NULL;
-               mutex_unlock(&dev->struct_mutex);
-               if (par)
-                       drm_fb_helper_free(&par->helper);
+               if (info->cmap.len)
+                       fb_dealloc_cmap(&info->cmap);
                framebuffer_release(info);
        }
 
+       if (nouveau_fb->nvbo) {
+               nouveau_bo_unmap(nouveau_fb->nvbo);
+               drm_gem_object_unreference_unlocked(nouveau_fb->nvbo->gem);
+               nouveau_fb->nvbo = NULL;
+       }
+       drm_fb_helper_fini(&nfbdev->helper);
+       drm_framebuffer_cleanup(&nouveau_fb->base);
        return 0;
 }
 
 void nouveau_fbcon_gpu_lockup(struct fb_info *info)
 {
-       struct nouveau_fbcon_par *par = info->par;
-       struct drm_device *dev = par->dev;
+       struct nouveau_fbdev *nfbdev = info->par;
+       struct drm_device *dev = nfbdev->dev;
 
        NV_ERROR(dev, "GPU lockup - switching to software fbcon\n");
        info->flags |= FBINFO_HWACCEL_DISABLED;
 }
+
+static struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
+       .gamma_set = nouveau_fbcon_gamma_set,
+       .gamma_get = nouveau_fbcon_gamma_get,
+       .fb_probe = nouveau_fbcon_find_or_create_single,
+};
+
+
+int nouveau_fbcon_init(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_fbdev *nfbdev;
+       int ret;
+
+       nfbdev = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
+       if (!nfbdev)
+               return -ENOMEM;
+
+       nfbdev->dev = dev;
+       dev_priv->nfbdev = nfbdev;
+       nfbdev->helper.funcs = &nouveau_fbcon_helper_funcs;
+
+       ret = drm_fb_helper_init(dev, &nfbdev->helper, 2, 4);
+       if (ret) {
+               kfree(nfbdev);
+               return ret;
+       }
+
+       drm_fb_helper_single_add_all_connectors(&nfbdev->helper);
+       drm_fb_helper_initial_config(&nfbdev->helper, 32);
+       return 0;
+}
+
+void nouveau_fbcon_fini(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+
+       if (!dev_priv->nfbdev)
+               return;
+
+       nouveau_fbcon_destroy(dev, dev_priv->nfbdev);
+       kfree(dev_priv->nfbdev);
+       dev_priv->nfbdev = NULL;
+}
+
+void nouveau_fbcon_save_disable_accel(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+
+       dev_priv->nfbdev->saved_flags = dev_priv->nfbdev->helper.fbdev->flags;
+       dev_priv->nfbdev->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
+}
+
+void nouveau_fbcon_restore_accel(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       dev_priv->nfbdev->helper.fbdev->flags = dev_priv->nfbdev->saved_flags;
+}
+
+void nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       fb_set_suspend(dev_priv->nfbdev->helper.fbdev, state);
+}
+
+void nouveau_fbcon_zfill_all(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       nouveau_fbcon_zfill(dev, dev_priv->nfbdev);
+}