drm: disable encoders before re-routing them
authorJesse Barnes <jbarnes@virtuousgeek.org>
Tue, 24 Feb 2009 00:09:34 +0000 (16:09 -0800)
committerDave Airlie <airlied@linux.ie>
Wed, 25 Feb 2009 04:42:23 +0000 (14:42 +1000)
In some cases we may receive a mode config that has a different
CRTC<->encoder map that the current configuration.  In that case, we
need to disable any re-routed encoders before setting the mode,
otherwise they may not pick up the new CRTC (if the output types are
incompatible for example).

Tested-by: Kristian Høgsberg <krh@bitplanet.net>
Signed-off-by: Jesse Barnes <jbarnes@virtuousgeek.org>
Signed-off-by: Dave Airlie <airlied@linux.ie>
drivers/gpu/drm/drm_crtc_helper.c
include/drm/drm_crtc_helper.h

index 733028b4d45e413e9d1689cd73a5fdec0618a982..1c3a8c5571408a6f0a698a53b58f93f6f471a9a9 100644 (file)
@@ -452,6 +452,59 @@ static void drm_setup_crtcs(struct drm_device *dev)
        kfree(modes);
        kfree(enabled);
 }
+
+/**
+ * drm_encoder_crtc_ok - can a given crtc drive a given encoder?
+ * @encoder: encoder to test
+ * @crtc: crtc to test
+ *
+ * Return false if @encoder can't be driven by @crtc, true otherwise.
+ */
+static bool drm_encoder_crtc_ok(struct drm_encoder *encoder,
+                               struct drm_crtc *crtc)
+{
+       struct drm_device *dev;
+       struct drm_crtc *tmp;
+       int crtc_mask = 1;
+
+       WARN(!crtc, "checking null crtc?");
+
+       dev = crtc->dev;
+
+       list_for_each_entry(tmp, &dev->mode_config.crtc_list, head) {
+               if (tmp == crtc)
+                       break;
+               crtc_mask <<= 1;
+       }
+
+       if (encoder->possible_crtcs & crtc_mask)
+               return true;
+       return false;
+}
+
+/*
+ * Check the CRTC we're going to map each output to vs. its current
+ * CRTC.  If they don't match, we have to disable the output and the CRTC
+ * since the driver will have to re-route things.
+ */
+static void
+drm_crtc_prepare_encoders(struct drm_device *dev)
+{
+       struct drm_encoder_helper_funcs *encoder_funcs;
+       struct drm_encoder *encoder;
+
+       list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+               encoder_funcs = encoder->helper_private;
+               /* Disable unused encoders */
+               if (encoder->crtc == NULL)
+                       (*encoder_funcs->dpms)(encoder, DRM_MODE_DPMS_OFF);
+               /* Disable encoders whose CRTC is about to change */
+               if (encoder_funcs->get_crtc &&
+                   encoder->crtc != (*encoder_funcs->get_crtc)(encoder))
+                       (*encoder_funcs->dpms)(encoder, DRM_MODE_DPMS_OFF);
+       }
+}
+
 /**
  * drm_crtc_set_mode - set a mode
  * @crtc: CRTC to program
@@ -547,6 +600,8 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
                encoder_funcs->prepare(encoder);
        }
 
+       drm_crtc_prepare_encoders(dev);
+
        crtc_funcs->prepare(crtc);
 
        /* Set up the DPLL and any encoders state that needs to adjust or depend
@@ -617,7 +672,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
        struct drm_device *dev;
        struct drm_crtc **save_crtcs, *new_crtc;
        struct drm_encoder **save_encoders, *new_encoder;
-       struct drm_framebuffer *old_fb;
+       struct drm_framebuffer *old_fb = NULL;
        bool save_enabled;
        bool mode_changed = false;
        bool fb_changed = false;
@@ -668,9 +723,10 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
         * and then just flip_or_move it */
        if (set->crtc->fb != set->fb) {
                /* If we have no fb then treat it as a full mode set */
-               if (set->crtc->fb == NULL)
+               if (set->crtc->fb == NULL) {
+                       DRM_DEBUG("crtc has no fb, full mode set\n");
                        mode_changed = true;
-               else if ((set->fb->bits_per_pixel !=
+               else if ((set->fb->bits_per_pixel !=
                         set->crtc->fb->bits_per_pixel) ||
                         set->fb->depth != set->crtc->fb->depth)
                        fb_changed = true;
@@ -682,7 +738,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
                fb_changed = true;
 
        if (set->mode && !drm_mode_equal(set->mode, &set->crtc->mode)) {
-               DRM_DEBUG("modes are different\n");
+               DRM_DEBUG("modes are different, full mode set\n");
                drm_mode_debug_printmodeline(&set->crtc->mode);
                drm_mode_debug_printmodeline(set->mode);
                mode_changed = true;
@@ -708,6 +764,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
                }
 
                if (new_encoder != connector->encoder) {
+                       DRM_DEBUG("encoder changed, full mode switch\n");
                        mode_changed = true;
                        connector->encoder = new_encoder;
                }
@@ -734,10 +791,20 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
                        if (set->connectors[ro] == connector)
                                new_crtc = set->crtc;
                }
+
+               /* Make sure the new CRTC will work with the encoder */
+               if (new_crtc &&
+                   !drm_encoder_crtc_ok(connector->encoder, new_crtc)) {
+                       ret = -EINVAL;
+                       goto fail_set_mode;
+               }
                if (new_crtc != connector->encoder->crtc) {
+                       DRM_DEBUG("crtc changed, full mode switch\n");
                        mode_changed = true;
                        connector->encoder->crtc = new_crtc;
                }
+               DRM_DEBUG("setting connector %d crtc to %p\n",
+                         connector->base.id, new_crtc);
        }
 
        /* mode_set_base is not a required function */
@@ -781,6 +848,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
 
 fail_set_mode:
        set->crtc->enabled = save_enabled;
+       set->crtc->fb = old_fb;
        count = 0;
        list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
                if (!connector->encoder)
index 0b0d236c215469120e031af56d5e80a4ca475e5c..c7d4b2e606a5f73f5fbed9217449b16c504b7e4d 100644 (file)
@@ -76,6 +76,7 @@ struct drm_encoder_helper_funcs {
        void (*mode_set)(struct drm_encoder *encoder,
                         struct drm_display_mode *mode,
                         struct drm_display_mode *adjusted_mode);
+       struct drm_crtc *(*get_crtc)(struct drm_encoder *encoder);
        /* detect for DAC style encoders */
        enum drm_connector_status (*detect)(struct drm_encoder *encoder,
                                            struct drm_connector *connector);