]> git.samba.org - sfrench/cifs-2.6.git/blobdiff - drivers/gpu/drm/drm_fb_helper.c
drm/fb: fix fbdev object model + cleanup properly.
[sfrench/cifs-2.6.git] / drivers / gpu / drm / drm_fb_helper.c
index 99487237111d152c2748edf1662bcf7187379883..055b5be7887720922c668df87dd9d86a01ba29ca 100644 (file)
@@ -747,32 +747,30 @@ EXPORT_SYMBOL(drm_fb_helper_pan_display);
 
 int drm_fb_helper_single_fb_probe(struct drm_device *dev,
                                  int preferred_bpp,
-                                 int (*fb_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 **fb_ptr))
+                                 int (*fb_find_or_create)(struct drm_device *dev,
+                                                          struct drm_fb_helper_surface_size *sizes,
+                                                          struct drm_fb_helper **fb_ptr))
 {
        struct drm_crtc *crtc;
        struct drm_connector *connector;
-       unsigned int fb_width = (unsigned)-1, fb_height = (unsigned)-1;
-       unsigned int surface_width = 0, surface_height = 0;
        int new_fb = 0;
        int crtc_count = 0;
        int ret, i, conn_count = 0;
        struct fb_info *info;
-       struct drm_framebuffer *fb;
        struct drm_mode_set *modeset = NULL;
        struct drm_fb_helper *fb_helper;
-       uint32_t surface_depth = 24, surface_bpp = 32;
+       struct drm_fb_helper_surface_size sizes;
+
+       memset(&sizes, 0, sizeof(struct drm_fb_helper_surface_size));
+       sizes.surface_depth = 24;
+       sizes.surface_bpp = 32;
+       sizes.fb_width = (unsigned)-1;
+       sizes.fb_height = (unsigned)-1;
 
        /* if driver picks 8 or 16 by default use that
           for both depth/bpp */
-       if (preferred_bpp != surface_bpp) {
-               surface_depth = surface_bpp = preferred_bpp;
+       if (preferred_bpp != sizes.surface_bpp) {
+               sizes.surface_depth = sizes.surface_bpp = preferred_bpp;
        }
        /* first up get a count of crtcs now in use and new min/maxes width/heights */
        list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
@@ -788,21 +786,21 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev,
                if (cmdline_mode->bpp_specified) {
                        switch (cmdline_mode->bpp) {
                        case 8:
-                               surface_depth = surface_bpp = 8;
+                               sizes.surface_depth = sizes.surface_bpp = 8;
                                break;
                        case 15:
-                               surface_depth = 15;
-                               surface_bpp = 16;
+                               sizes.surface_depth = 15;
+                               sizes.surface_bpp = 16;
                                break;
                        case 16:
-                               surface_depth = surface_bpp = 16;
+                               sizes.surface_depth = sizes.surface_bpp = 16;
                                break;
                        case 24:
-                               surface_depth = surface_bpp = 24;
+                               sizes.surface_depth = sizes.surface_bpp = 24;
                                break;
                        case 32:
-                               surface_depth = 24;
-                               surface_bpp = 32;
+                               sizes.surface_depth = 24;
+                               sizes.surface_bpp = 32;
                                break;
                        }
                        break;
@@ -812,59 +810,41 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev,
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
                if (drm_helper_crtc_in_use(crtc)) {
                        if (crtc->desired_mode) {
-                               if (crtc->desired_mode->hdisplay < fb_width)
-                                       fb_width = crtc->desired_mode->hdisplay;
+                               if (crtc->desired_mode->hdisplay < sizes.fb_width)
+                                       sizes.fb_width = crtc->desired_mode->hdisplay;
 
-                               if (crtc->desired_mode->vdisplay < fb_height)
-                                       fb_height = crtc->desired_mode->vdisplay;
+                               if (crtc->desired_mode->vdisplay < sizes.fb_height)
+                                       sizes.fb_height = crtc->desired_mode->vdisplay;
 
-                               if (crtc->desired_mode->hdisplay > surface_width)
-                                       surface_width = crtc->desired_mode->hdisplay;
+                               if (crtc->desired_mode->hdisplay > sizes.surface_width)
+                                       sizes.surface_width = crtc->desired_mode->hdisplay;
 
-                               if (crtc->desired_mode->vdisplay > surface_height)
-                                       surface_height = crtc->desired_mode->vdisplay;
+                               if (crtc->desired_mode->vdisplay > sizes.surface_height)
+                                       sizes.surface_height = crtc->desired_mode->vdisplay;
                        }
                        crtc_count++;
                }
        }
 
-       if (crtc_count == 0 || fb_width == -1 || fb_height == -1) {
+       if (crtc_count == 0 || sizes.fb_width == -1 || sizes.fb_height == -1) {
                /* hmm everyone went away - assume VGA cable just fell out
                   and will come back later. */
                return 0;
        }
 
-       /* do we have an fb already? */
-       if (list_empty(&dev->mode_config.fb_kernel_list)) {
-               ret = (*fb_create)(dev, fb_width, fb_height, surface_width,
-                                  surface_height, surface_depth, surface_bpp,
-                                  &fb);
-               if (ret)
-                       return -EINVAL;
-               new_fb = 1;
-       } else {
-               fb = list_first_entry(&dev->mode_config.fb_kernel_list,
-                                     struct drm_framebuffer, filp_head);
-
-               /* if someone hotplugs something bigger than we have already allocated, we are pwned.
-                  As really we can't resize an fbdev that is in the wild currently due to fbdev
-                  not really being designed for the lower layers moving stuff around under it.
-                  - so in the grand style of things - punt. */
-               if ((fb->width < surface_width) ||
-                   (fb->height < surface_height)) {
-                       DRM_ERROR("Framebuffer not large enough to scale console onto.\n");
-                       return -EINVAL;
-               }
-       }
+       /* push down into drivers */
+       new_fb = (*fb_find_or_create)(dev, &sizes,
+                                     &fb_helper);
+       if (new_fb < 0)
+               return new_fb;
 
-       info = fb->fbdev;
-       fb_helper = info->par;
+       info = fb_helper->fbdev;
 
        crtc_count = 0;
        /* okay we need to setup new connector sets in the crtcs */
        list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
                modeset = &fb_helper->crtc_info[crtc_count].mode_set;
-               modeset->fb = fb;
+               modeset->fb = fb_helper->fb;
                conn_count = 0;
                list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
                        if (connector->encoder)
@@ -891,7 +871,6 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev,
                }
        }
        fb_helper->crtc_count = crtc_count;
-       fb_helper->fb = fb;
 
        if (new_fb) {
                info->var.pixclock = 0;
@@ -902,11 +881,13 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev,
                        fb_dealloc_cmap(&info->cmap);
                        return -EINVAL;
                }
+
+               printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
+                      info->fix.id);
+
        } else {
                drm_fb_helper_set_par(info);
        }
-       printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
-              info->fix.id);
 
        /* Switch back to kernel console on panic */
        /* multi card linked list maybe */
@@ -916,7 +897,9 @@ int drm_fb_helper_single_fb_probe(struct drm_device *dev,
                                               &paniced);
                register_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
        }
-       list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
+       if (new_fb)
+               list_add(&fb_helper->kernel_fb_list, &kernel_fb_helper_list);
+
        return 0;
 }
 EXPORT_SYMBOL(drm_fb_helper_single_fb_probe);
@@ -931,7 +914,7 @@ void drm_fb_helper_free(struct drm_fb_helper *helper)
                unregister_sysrq_key('v', &sysrq_drm_fb_helper_restore_op);
        }
        drm_fb_helper_crtc_free(helper);
-       fb_dealloc_cmap(&helper->fb->fbdev->cmap);
+       fb_dealloc_cmap(&helper->fbdev->cmap);
 }
 EXPORT_SYMBOL(drm_fb_helper_free);
 
@@ -953,10 +936,11 @@ void drm_fb_helper_fill_fix(struct fb_info *info, uint32_t pitch,
 }
 EXPORT_SYMBOL(drm_fb_helper_fill_fix);
 
-void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
+void drm_fb_helper_fill_var(struct fb_info *info, struct drm_fb_helper *fb_helper,
                            uint32_t fb_width, uint32_t fb_height)
 {
-       info->pseudo_palette = fb->pseudo_palette;
+       struct drm_framebuffer *fb = fb_helper->fb;
+       info->pseudo_palette = fb_helper->pseudo_palette;
        info->var.xres_virtual = fb->width;
        info->var.yres_virtual = fb->height;
        info->var.bits_per_pixel = fb->bits_per_pixel;
@@ -1024,3 +1008,364 @@ void drm_fb_helper_fill_var(struct fb_info *info, struct drm_framebuffer *fb,
        info->var.yres = fb_height;
 }
 EXPORT_SYMBOL(drm_fb_helper_fill_var);
+
+static int drm_helper_probe_connector_modes(struct drm_device *dev, uint32_t maxX,
+                                           uint32_t maxY)
+{
+       struct drm_connector *connector;
+       int count = 0;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               count += connector->funcs->fill_modes(connector, maxX, maxY);
+       }
+
+       return count;
+}
+
+static struct drm_display_mode *drm_has_preferred_mode(struct drm_connector *connector, int width, int height)
+{
+       struct drm_display_mode *mode;
+
+       list_for_each_entry(mode, &connector->modes, head) {
+               if (drm_mode_width(mode) > width ||
+                   drm_mode_height(mode) > height)
+                       continue;
+               if (mode->type & DRM_MODE_TYPE_PREFERRED)
+                       return mode;
+       }
+       return NULL;
+}
+
+static bool drm_has_cmdline_mode(struct drm_connector *connector)
+{
+       struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
+       struct drm_fb_helper_cmdline_mode *cmdline_mode;
+
+       if (!fb_help_conn)
+               return false;
+
+       cmdline_mode = &fb_help_conn->cmdline_mode;
+       return cmdline_mode->specified;
+}
+
+static struct drm_display_mode *drm_pick_cmdline_mode(struct drm_connector *connector, int width, int height)
+{
+       struct drm_fb_helper_connector *fb_help_conn = connector->fb_helper_private;
+       struct drm_fb_helper_cmdline_mode *cmdline_mode;
+       struct drm_display_mode *mode = NULL;
+
+       if (!fb_help_conn)
+               return mode;
+
+       cmdline_mode = &fb_help_conn->cmdline_mode;
+       if (cmdline_mode->specified == false)
+               return mode;
+
+       /* attempt to find a matching mode in the list of modes
+        *  we have gotten so far, if not add a CVT mode that conforms
+        */
+       if (cmdline_mode->rb || cmdline_mode->margins)
+               goto create_mode;
+
+       list_for_each_entry(mode, &connector->modes, head) {
+               /* check width/height */
+               if (mode->hdisplay != cmdline_mode->xres ||
+                   mode->vdisplay != cmdline_mode->yres)
+                       continue;
+
+               if (cmdline_mode->refresh_specified) {
+                       if (mode->vrefresh != cmdline_mode->refresh)
+                               continue;
+               }
+
+               if (cmdline_mode->interlace) {
+                       if (!(mode->flags & DRM_MODE_FLAG_INTERLACE))
+                               continue;
+               }
+               return mode;
+       }
+
+create_mode:
+       mode = drm_cvt_mode(connector->dev, cmdline_mode->xres,
+                           cmdline_mode->yres,
+                           cmdline_mode->refresh_specified ? cmdline_mode->refresh : 60,
+                           cmdline_mode->rb, cmdline_mode->interlace,
+                           cmdline_mode->margins);
+       drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V);
+       list_add(&mode->head, &connector->modes);
+       return mode;
+}
+
+static bool drm_connector_enabled(struct drm_connector *connector, bool strict)
+{
+       bool enable;
+
+       if (strict) {
+               enable = connector->status == connector_status_connected;
+       } else {
+               enable = connector->status != connector_status_disconnected;
+       }
+       return enable;
+}
+
+static void drm_enable_connectors(struct drm_device *dev, bool *enabled)
+{
+       bool any_enabled = false;
+       struct drm_connector *connector;
+       int i = 0;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               enabled[i] = drm_connector_enabled(connector, true);
+               DRM_DEBUG_KMS("connector %d enabled? %s\n", connector->base.id,
+                         enabled[i] ? "yes" : "no");
+               any_enabled |= enabled[i];
+               i++;
+       }
+
+       if (any_enabled)
+               return;
+
+       i = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               enabled[i] = drm_connector_enabled(connector, false);
+               i++;
+       }
+}
+
+static bool drm_target_preferred(struct drm_device *dev,
+                                struct drm_display_mode **modes,
+                                bool *enabled, int width, int height)
+{
+       struct drm_connector *connector;
+       int i = 0;
+
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+
+               if (enabled[i] == false) {
+                       i++;
+                       continue;
+               }
+
+               DRM_DEBUG_KMS("looking for cmdline mode on connector %d\n",
+                             connector->base.id);
+
+               /* got for command line mode first */
+               modes[i] = drm_pick_cmdline_mode(connector, width, height);
+               if (!modes[i]) {
+                       DRM_DEBUG_KMS("looking for preferred mode on connector %d\n",
+                                     connector->base.id);
+                       modes[i] = drm_has_preferred_mode(connector, width, height);
+               }
+               /* No preferred modes, pick one off the list */
+               if (!modes[i] && !list_empty(&connector->modes)) {
+                       list_for_each_entry(modes[i], &connector->modes, head)
+                               break;
+               }
+               DRM_DEBUG_KMS("found mode %s\n", modes[i] ? modes[i]->name :
+                         "none");
+               i++;
+       }
+       return true;
+}
+
+static int drm_pick_crtcs(struct drm_device *dev,
+                         struct drm_crtc **best_crtcs,
+                         struct drm_display_mode **modes,
+                         int n, int width, int height)
+{
+       int c, o;
+       struct drm_connector *connector;
+       struct drm_connector_helper_funcs *connector_funcs;
+       struct drm_encoder *encoder;
+       struct drm_crtc *best_crtc;
+       int my_score, best_score, score;
+       struct drm_crtc **crtcs, *crtc;
+
+       if (n == dev->mode_config.num_connector)
+               return 0;
+       c = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               if (c == n)
+                       break;
+               c++;
+       }
+
+       best_crtcs[n] = NULL;
+       best_crtc = NULL;
+       best_score = drm_pick_crtcs(dev, best_crtcs, modes, n+1, width, height);
+       if (modes[n] == NULL)
+               return best_score;
+
+       crtcs = kmalloc(dev->mode_config.num_connector *
+                       sizeof(struct drm_crtc *), GFP_KERNEL);
+       if (!crtcs)
+               return best_score;
+
+       my_score = 1;
+       if (connector->status == connector_status_connected)
+               my_score++;
+       if (drm_has_cmdline_mode(connector))
+               my_score++;
+       if (drm_has_preferred_mode(connector, width, height))
+               my_score++;
+
+       connector_funcs = connector->helper_private;
+       encoder = connector_funcs->best_encoder(connector);
+       if (!encoder)
+               goto out;
+
+       connector->encoder = encoder;
+
+       /* select a crtc for this connector and then attempt to configure
+          remaining connectors */
+       c = 0;
+       list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+
+               if ((encoder->possible_crtcs & (1 << c)) == 0) {
+                       c++;
+                       continue;
+               }
+
+               for (o = 0; o < n; o++)
+                       if (best_crtcs[o] == crtc)
+                               break;
+
+               if (o < n) {
+                       /* ignore cloning for now */
+                       c++;
+                       continue;
+               }
+
+               crtcs[n] = crtc;
+               memcpy(crtcs, best_crtcs, n * sizeof(struct drm_crtc *));
+               score = my_score + drm_pick_crtcs(dev, crtcs, modes, n + 1,
+                                                 width, height);
+               if (score > best_score) {
+                       best_crtc = crtc;
+                       best_score = score;
+                       memcpy(best_crtcs, crtcs,
+                              dev->mode_config.num_connector *
+                              sizeof(struct drm_crtc *));
+               }
+               c++;
+       }
+out:
+       kfree(crtcs);
+       return best_score;
+}
+
+static void drm_setup_crtcs(struct drm_device *dev)
+{
+       struct drm_crtc **crtcs;
+       struct drm_display_mode **modes;
+       struct drm_encoder *encoder;
+       struct drm_connector *connector;
+       bool *enabled;
+       int width, height;
+       int i, ret;
+
+       DRM_DEBUG_KMS("\n");
+
+       width = dev->mode_config.max_width;
+       height = dev->mode_config.max_height;
+
+       /* clean out all the encoder/crtc combos */
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+               encoder->crtc = NULL;
+       }
+
+       crtcs = kcalloc(dev->mode_config.num_connector,
+                       sizeof(struct drm_crtc *), GFP_KERNEL);
+       modes = kcalloc(dev->mode_config.num_connector,
+                       sizeof(struct drm_display_mode *), GFP_KERNEL);
+       enabled = kcalloc(dev->mode_config.num_connector,
+                         sizeof(bool), GFP_KERNEL);
+
+       drm_enable_connectors(dev, enabled);
+
+       ret = drm_target_preferred(dev, modes, enabled, width, height);
+       if (!ret)
+               DRM_ERROR("Unable to find initial modes\n");
+
+       DRM_DEBUG_KMS("picking CRTCs for %dx%d config\n", width, height);
+
+       drm_pick_crtcs(dev, crtcs, modes, 0, width, height);
+
+       i = 0;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               struct drm_display_mode *mode = modes[i];
+               struct drm_crtc *crtc = crtcs[i];
+
+               if (connector->encoder == NULL) {
+                       i++;
+                       continue;
+               }
+
+               if (mode && crtc) {
+                       DRM_DEBUG_KMS("desired mode %s set on crtc %d\n",
+                                 mode->name, crtc->base.id);
+                       crtc->desired_mode = mode;
+                       connector->encoder->crtc = crtc;
+               } else {
+                       connector->encoder->crtc = NULL;
+                       connector->encoder = NULL;
+               }
+               i++;
+       }
+
+       kfree(crtcs);
+       kfree(modes);
+       kfree(enabled);
+}
+
+/**
+ * drm_helper_initial_config - setup a sane initial connector configuration
+ * @dev: DRM device
+ *
+ * LOCKING:
+ * Called at init time, must take mode config lock.
+ *
+ * Scan the CRTCs and connectors and try to put together an initial setup.
+ * At the moment, this is a cloned configuration across all heads with
+ * a new framebuffer object as the backing store.
+ *
+ * RETURNS:
+ * Zero if everything went ok, nonzero otherwise.
+ */
+bool drm_helper_initial_config(struct drm_device *dev)
+{
+       int count = 0;
+
+       /* disable all the possible outputs/crtcs before entering KMS mode */
+       drm_helper_disable_unused_functions(dev);
+
+       drm_fb_helper_parse_command_line(dev);
+
+       count = drm_helper_probe_connector_modes(dev,
+                                                dev->mode_config.max_width,
+                                                dev->mode_config.max_height);
+
+       /*
+        * we shouldn't end up with no modes here.
+        */
+       if (count == 0)
+               printk(KERN_INFO "No connectors reported connected with modes\n");
+
+       drm_setup_crtcs(dev);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_helper_initial_config);
+
+bool drm_helper_fb_hotplug_event(struct drm_device *dev)
+{
+       DRM_DEBUG_KMS("\n");
+
+       drm_helper_probe_connector_modes(dev, dev->mode_config.max_width,
+                                        dev->mode_config.max_height);
+
+       drm_setup_crtcs(dev);
+
+       return true;
+}
+EXPORT_SYMBOL(drm_helper_fb_hotplug_event);