Merge remote-tracking branch 'drm/drm-next'
[linux] / drivers / gpu / drm / drm_fb_helper.c
index d73703a..145c00a 100644 (file)
@@ -1874,6 +1874,7 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
        int i;
        struct drm_fb_helper_surface_size sizes;
        int gamma_size = 0;
+       int best_depth = 0;
 
        memset(&sizes, 0, sizeof(struct drm_fb_helper_surface_size));
        sizes.surface_depth = 24;
@@ -1881,7 +1882,10 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
        sizes.fb_width = (u32)-1;
        sizes.fb_height = (u32)-1;
 
-       /* if driver picks 8 or 16 by default use that for both depth/bpp */
+       /*
+        * If driver picks 8 or 16 by default use that for both depth/bpp
+        * to begin with
+        */
        if (preferred_bpp != sizes.surface_bpp)
                sizes.surface_depth = sizes.surface_bpp = preferred_bpp;
 
@@ -1916,6 +1920,55 @@ static int drm_fb_helper_single_fb_probe(struct drm_fb_helper *fb_helper,
                }
        }
 
+       /*
+        * If we run into a situation where, for example, the primary plane
+        * supports RGBA5551 (16 bpp, depth 15) but not RGB565 (16 bpp, depth
+        * 16) we need to scale down the depth of the sizes we request.
+        */
+       for (i = 0; i < fb_helper->crtc_count; i++) {
+               struct drm_mode_set *mode_set = &fb_helper->crtc_info[i].mode_set;
+               struct drm_crtc *crtc = mode_set->crtc;
+               struct drm_plane *plane = crtc->primary;
+               int j;
+
+               DRM_DEBUG("test CRTC %d primary plane\n", i);
+
+               for (j = 0; j < plane->format_count; j++) {
+                       const struct drm_format_info *fmt;
+
+                       fmt = drm_format_info(plane->format_types[j]);
+
+                       /*
+                        * Do not consider YUV or other complicated formats
+                        * for framebuffers. This means only legacy formats
+                        * are supported (fmt->depth is a legacy field) but
+                        * the framebuffer emulation can only deal with such
+                        * formats, specifically RGB/BGA formats.
+                        */
+                       if (fmt->depth == 0)
+                               continue;
+
+                       /* We found a perfect fit, great */
+                       if (fmt->depth == sizes.surface_depth) {
+                               best_depth = fmt->depth;
+                               break;
+                       }
+
+                       /* Skip depths above what we're looking for */
+                       if (fmt->depth > sizes.surface_depth)
+                               continue;
+
+                       /* Best depth found so far */
+                       if (fmt->depth > best_depth)
+                               best_depth = fmt->depth;
+               }
+       }
+       if (sizes.surface_depth != best_depth) {
+               DRM_INFO("requested bpp %d, scaled depth down to %d",
+                        sizes.surface_bpp, best_depth);
+               sizes.surface_depth = best_depth;
+       }
+
        crtc_count = 0;
        for (i = 0; i < fb_helper->crtc_count; i++) {
                struct drm_display_mode *desired_mode;
@@ -2891,7 +2944,7 @@ int drm_fb_helper_fbdev_setup(struct drm_device *dev,
        return 0;
 
 err_drm_fb_helper_fini:
-       drm_fb_helper_fini(fb_helper);
+       drm_fb_helper_fbdev_teardown(dev);
 
        return ret;
 }
@@ -2986,18 +3039,16 @@ static int drm_fbdev_fb_release(struct fb_info *info, int user)
        return 0;
 }
 
-/*
- * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
- * unregister_framebuffer() or fb_release().
- */
-static void drm_fbdev_fb_destroy(struct fb_info *info)
+static void drm_fbdev_cleanup(struct drm_fb_helper *fb_helper)
 {
-       struct drm_fb_helper *fb_helper = info->par;
        struct fb_info *fbi = fb_helper->fbdev;
        struct fb_ops *fbops = NULL;
        void *shadow = NULL;
 
-       if (fbi->fbdefio) {
+       if (!fb_helper->dev)
+               return;
+
+       if (fbi && fbi->fbdefio) {
                fb_deferred_io_cleanup(fbi);
                shadow = fbi->screen_buffer;
                fbops = fbi->fbops;
@@ -3011,15 +3062,22 @@ static void drm_fbdev_fb_destroy(struct fb_info *info)
        }
 
        drm_client_framebuffer_delete(fb_helper->buffer);
-       /*
-        * FIXME:
-        * Remove conditional when all CMA drivers have been moved over to using
-        * drm_fbdev_generic_setup().
-        */
-       if (fb_helper->client.funcs) {
-               drm_client_release(&fb_helper->client);
-               kfree(fb_helper);
-       }
+}
+
+static void drm_fbdev_release(struct drm_fb_helper *fb_helper)
+{
+       drm_fbdev_cleanup(fb_helper);
+       drm_client_release(&fb_helper->client);
+       kfree(fb_helper);
+}
+
+/*
+ * fb_ops.fb_destroy is called by the last put_fb_info() call at the end of
+ * unregister_framebuffer() or fb_release().
+ */
+static void drm_fbdev_fb_destroy(struct fb_info *info)
+{
+       drm_fbdev_release(info->par);
 }
 
 static int drm_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
@@ -3072,7 +3130,6 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
        struct drm_framebuffer *fb;
        struct fb_info *fbi;
        u32 format;
-       int ret;
 
        DRM_DEBUG_KMS("surface width(%d), height(%d) and bpp(%d)\n",
                      sizes->surface_width, sizes->surface_height,
@@ -3089,10 +3146,8 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
        fb = buffer->fb;
 
        fbi = drm_fb_helper_alloc_fbi(fb_helper);
-       if (IS_ERR(fbi)) {
-               ret = PTR_ERR(fbi);
-               goto err_free_buffer;
-       }
+       if (IS_ERR(fbi))
+               return PTR_ERR(fbi);
 
        fbi->par = fb_helper;
        fbi->fbops = &drm_fbdev_fb_ops;
@@ -3123,8 +3178,7 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
                if (!fbops || !shadow) {
                        kfree(fbops);
                        vfree(shadow);
-                       ret = -ENOMEM;
-                       goto err_fb_info_destroy;
+                       return -ENOMEM;
                }
 
                *fbops = *fbi->fbops;
@@ -3136,13 +3190,6 @@ int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
        }
 
        return 0;
-
-err_fb_info_destroy:
-       drm_fb_helper_fini(fb_helper);
-err_free_buffer:
-       drm_client_framebuffer_delete(buffer);
-
-       return ret;
 }
 EXPORT_SYMBOL(drm_fb_helper_generic_probe);
 
@@ -3154,25 +3201,16 @@ static void drm_fbdev_client_unregister(struct drm_client_dev *client)
 {
        struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
 
-       if (fb_helper->fbdev) {
-               drm_fb_helper_unregister_fbi(fb_helper);
+       if (fb_helper->fbdev)
                /* drm_fbdev_fb_destroy() takes care of cleanup */
-               return;
-       }
-
-       /* Did drm_fb_helper_fbdev_setup() run? */
-       if (fb_helper->dev)
-               drm_fb_helper_fini(fb_helper);
-
-       drm_client_release(client);
-       kfree(fb_helper);
+               drm_fb_helper_unregister_fbi(fb_helper);
+       else
+               drm_fbdev_release(fb_helper);
 }
 
 static int drm_fbdev_client_restore(struct drm_client_dev *client)
 {
-       struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client);
-
-       drm_fb_helper_restore_fbdev_mode_unlocked(fb_helper);
+       drm_fb_helper_lastclose(client->dev);
 
        return 0;
 }
@@ -3183,7 +3221,7 @@ static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
        struct drm_device *dev = client->dev;
        int ret;
 
-       /* If drm_fb_helper_fbdev_setup() failed, we only try once */
+       /* Setup is not retried if it has failed */
        if (!fb_helper->dev && fb_helper->funcs)
                return 0;
 
@@ -3195,15 +3233,34 @@ static int drm_fbdev_client_hotplug(struct drm_client_dev *client)
                return 0;
        }
 
-       ret = drm_fb_helper_fbdev_setup(dev, fb_helper, &drm_fb_helper_generic_funcs,
-                                       fb_helper->preferred_bpp, 0);
-       if (ret) {
-               fb_helper->dev = NULL;
-               fb_helper->fbdev = NULL;
-               return ret;
-       }
+       drm_fb_helper_prepare(dev, fb_helper, &drm_fb_helper_generic_funcs);
+
+       ret = drm_fb_helper_init(dev, fb_helper, dev->mode_config.num_connector);
+       if (ret)
+               goto err;
+
+       ret = drm_fb_helper_single_add_all_connectors(fb_helper);
+       if (ret)
+               goto err_cleanup;
+
+       if (!drm_drv_uses_atomic_modeset(dev))
+               drm_helper_disable_unused_functions(dev);
+
+       ret = drm_fb_helper_initial_config(fb_helper, fb_helper->preferred_bpp);
+       if (ret)
+               goto err_cleanup;
 
        return 0;
+
+err_cleanup:
+       drm_fbdev_cleanup(fb_helper);
+err:
+       fb_helper->dev = NULL;
+       fb_helper->fbdev = NULL;
+
+       DRM_DEV_ERROR(dev->dev, "fbdev: Failed to setup generic emulation (ret=%d)\n", ret);
+
+       return ret;
 }
 
 static const struct drm_client_funcs drm_fbdev_client_funcs = {
@@ -3262,6 +3319,10 @@ int drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp)
 
        drm_client_add(&fb_helper->client);
 
+       if (!preferred_bpp)
+               preferred_bpp = dev->mode_config.preferred_depth;
+       if (!preferred_bpp)
+               preferred_bpp = 32;
        fb_helper->preferred_bpp = preferred_bpp;
 
        ret = drm_fbdev_client_hotplug(&fb_helper->client);