drm/kms/fb: move to using fb helper crtc grouping instead of core crtc list
[safe/jmp/linux-2.6] / drivers / gpu / drm / nouveau / nouveau_fbcon.c
index 0b05c86..90843b6 100644 (file)
@@ -36,6 +36,7 @@
 #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 +53,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;
@@ -107,6 +108,34 @@ 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_setcolreg = drm_fb_helper_setcolreg,
+       .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_setcolreg = drm_fb_helper_setcolreg,
+       .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)
 {
@@ -170,11 +199,10 @@ not_fb:
 }
 #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,14 +218,12 @@ 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;
@@ -205,13 +231,13 @@ nouveau_fbcon_create(struct drm_device *dev, uint32_t fb_width,
        struct device *device = &dev->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,35 +266,31 @@ 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);
+       info->par = nfbdev;
 
-       nouveau_fb = nouveau_framebuffer(fb);
-       *pfb = fb;
+       nouveau_framebuffer_init(dev, &nfbdev->nouveau_fb, &mode_cmd, nvbo);
 
-       info = framebuffer_alloc(sizeof(struct nouveau_fbcon_par), device);
-       if (!info) {
-               ret = -ENOMEM;
-               goto out_unref;
-       }
+       nouveau_fb = &nfbdev->nouveau_fb;
+       fb = &nouveau_fb->base;
 
-       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;
+       /* setup helper */
+       nfbdev->helper.fb = fb;
+       nfbdev->helper.fbdev = info;
+       nfbdev->helper.funcs = &nouveau_fbcon_helper_funcs;
 
        strcpy(info->fix.id, "nouveaufb");
-       info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
-                     FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_IMAGEBLIT;
+       if (nouveau_nofbaccel)
+               info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_DISABLED;
+       else
+               info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_COPYAREA |
+                             FBINFO_HWACCEL_FILLRECT |
+                             FBINFO_HWACCEL_IMAGEBLIT;
        info->fbops = &nouveau_fbcon_ops;
        info->fix.smem_start = dev->mode_config.fb_base + nvbo->bo.offset -
                               dev_priv->vm_vram_base;
@@ -278,7 +300,7 @@ 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);
@@ -311,23 +333,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) {
+       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",
@@ -336,6 +355,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:
@@ -344,46 +364,116 @@ 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);
+static int
+nouveau_fbcon_probe(struct nouveau_fbdev *nfbdev)
+{
+       NV_DEBUG_KMS(nfbdev->dev, "\n");
+
+       return drm_fb_helper_single_fb_probe(&nfbdev->helper, 32);
 }
 
 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);
                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_free(&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;
 }
+
+int nouveau_fbcon_init(struct drm_device *dev)
+{
+       struct drm_nouveau_private *dev_priv = dev->dev_private;
+       struct nouveau_fbdev *nfbdev;
+
+       nfbdev = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
+       if (!nfbdev)
+               return -ENOMEM;
+
+       nfbdev->dev = dev;
+       dev_priv->nfbdev = nfbdev;
+
+       drm_fb_helper_init_crtc_count(dev, &nfbdev->helper,
+                                     2, 4);
+       nfbdev->helper.fb_probe = nouveau_fbcon_find_or_create_single;
+       drm_fb_helper_initial_config(&nfbdev->helper);
+       nouveau_fbcon_probe(nfbdev);
+       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);
+}