drm: Add drm_bridge between encoder and connector
authorSean Paul <seanpaul@chromium.org>
Fri, 22 Feb 2013 19:01:26 +0000 (14:01 -0500)
committerChromeBot <chrome-bot@google.com>
Wed, 27 Feb 2013 03:10:51 +0000 (19:10 -0800)
This patch adds the notion of a drm_bridge between the encoder and the
connector. This allows us to handle bridge chips gracefully with dpms
and hotplug.

BUG=chrome-os-partner:17557
TEST=Tested on snow

Change-Id: I66b9f631bb924e443e79419a3dc3af0ef41fb70d
Signed-off-by: Sean Paul <seanpaul@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/43912

drivers/gpu/drm/drm_crtc.c
drivers/gpu/drm/drm_crtc_helper.c
include/drm/drm_crtc.h
include/drm/drm_crtc_helper.h

index c7ea0ef..570c79d 100644 (file)
@@ -551,6 +551,41 @@ void drm_connector_unplug_all(struct drm_device *dev)
 }
 EXPORT_SYMBOL(drm_connector_unplug_all);
 
+int drm_bridge_init(struct drm_device *dev, struct drm_bridge *bridge,
+               const struct drm_bridge_funcs *funcs)
+{
+       int ret;
+
+       mutex_lock(&dev->mode_config.mutex);
+
+       ret = drm_mode_object_get(dev, &bridge->base, DRM_MODE_OBJECT_BRIDGE);
+       if (ret)
+               goto out;
+
+       bridge->dev = dev;
+       bridge->funcs = funcs;
+
+       list_add_tail(&bridge->head, &dev->mode_config.bridge_list);
+       dev->mode_config.num_bridge++;
+
+ out:
+       mutex_unlock(&dev->mode_config.mutex);
+       return ret;
+}
+EXPORT_SYMBOL(drm_bridge_init);
+
+void drm_bridge_cleanup(struct drm_bridge *bridge)
+{
+       struct drm_device *dev = bridge->dev;
+
+       mutex_lock(&dev->mode_config.mutex);
+       drm_mode_object_put(dev, &bridge->base);
+       list_del(&bridge->head);
+       dev->mode_config.num_bridge--;
+       mutex_unlock(&dev->mode_config.mutex);
+}
+EXPORT_SYMBOL(drm_bridge_cleanup);
+
 int drm_encoder_init(struct drm_device *dev,
                      struct drm_encoder *encoder,
                      const struct drm_encoder_funcs *funcs,
@@ -926,6 +961,7 @@ void drm_mode_config_init(struct drm_device *dev)
        INIT_LIST_HEAD(&dev->mode_config.fb_list);
        INIT_LIST_HEAD(&dev->mode_config.crtc_list);
        INIT_LIST_HEAD(&dev->mode_config.connector_list);
+       INIT_LIST_HEAD(&dev->mode_config.bridge_list);
        INIT_LIST_HEAD(&dev->mode_config.encoder_list);
        INIT_LIST_HEAD(&dev->mode_config.property_list);
        INIT_LIST_HEAD(&dev->mode_config.property_blob_list);
@@ -939,6 +975,7 @@ void drm_mode_config_init(struct drm_device *dev)
        /* Just to be sure */
        dev->mode_config.num_fb = 0;
        dev->mode_config.num_connector = 0;
+       dev->mode_config.num_bridge = 0;
        dev->mode_config.num_crtc = 0;
        dev->mode_config.num_encoder = 0;
 }
@@ -951,6 +988,7 @@ int drm_mode_group_init(struct drm_device *dev, struct drm_mode_group *group)
        total_objects += dev->mode_config.num_crtc;
        total_objects += dev->mode_config.num_connector;
        total_objects += dev->mode_config.num_encoder;
+       total_objects += dev->mode_config.num_bridge;
 
        group->id_list = kzalloc(total_objects * sizeof(uint32_t), GFP_KERNEL);
        if (!group->id_list)
@@ -959,6 +997,7 @@ int drm_mode_group_init(struct drm_device *dev, struct drm_mode_group *group)
        group->num_crtcs = 0;
        group->num_connectors = 0;
        group->num_encoders = 0;
+       group->num_bridges = 0;
        return 0;
 }
 
@@ -968,6 +1007,7 @@ int drm_mode_group_init_legacy_group(struct drm_device *dev,
        struct drm_crtc *crtc;
        struct drm_encoder *encoder;
        struct drm_connector *connector;
+       struct drm_bridge *bridge;
        int ret;
 
        if ((ret = drm_mode_group_init(dev, group)))
@@ -984,6 +1024,11 @@ int drm_mode_group_init_legacy_group(struct drm_device *dev,
                group->id_list[group->num_crtcs + group->num_encoders +
                               group->num_connectors++] = connector->base.id;
 
+       list_for_each_entry(bridge, &dev->mode_config.bridge_list, head)
+               group->id_list[group->num_crtcs + group->num_encoders +
+                              group->num_connectors + group->num_bridges++] =
+                                       bridge->base.id;
+
        return 0;
 }
 EXPORT_SYMBOL(drm_mode_group_init_legacy_group);
@@ -1003,6 +1048,7 @@ EXPORT_SYMBOL(drm_mode_group_init_legacy_group);
 void drm_mode_config_cleanup(struct drm_device *dev)
 {
        struct drm_connector *connector, *ot;
+       struct drm_bridge *bridge, *bt;
        struct drm_crtc *crtc, *ct;
        struct drm_encoder *encoder, *enct;
        struct drm_framebuffer *fb, *fbt;
@@ -1019,6 +1065,11 @@ void drm_mode_config_cleanup(struct drm_device *dev)
                connector->funcs->destroy(connector);
        }
 
+       list_for_each_entry_safe(bridge, bt,
+                                &dev->mode_config.bridge_list, head) {
+               bridge->funcs->destroy(bridge);
+       }
+
        list_for_each_entry_safe(property, pt, &dev->mode_config.property_list,
                                 head) {
                drm_property_destroy(dev, property);
index ec8a192..26e48d2 100644 (file)
@@ -169,6 +169,29 @@ prune:
 }
 EXPORT_SYMBOL(drm_helper_probe_single_connector_modes);
 
+/**
+ * drm_helper_bridge_in_use - check if a given bridge is in use
+ * @bridge: bridge to check
+ *
+ * LOCKING:
+ * Caller must hold mode config lock.
+ *
+ * Walk @bridge's DRM device's mode_config and see if it's in use.
+ *
+ * RETURNS:
+ * True if @bridge is part of the mode_config, false otherwise.
+ */
+bool drm_helper_bridge_in_use(struct drm_bridge *bridge)
+{
+       struct drm_connector *connector;
+       struct drm_device *dev = bridge->dev;
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head)
+               if (connector->bridge == bridge)
+                       return true;
+       return false;
+}
+EXPORT_SYMBOL(drm_helper_bridge_in_use);
+
 /**
  * drm_helper_encoder_in_use - check if a given encoder is in use
  * @encoder: encoder to check
@@ -239,6 +262,8 @@ drm_encoder_disable(struct drm_encoder *encoder)
  */
 void drm_helper_disable_unused_functions(struct drm_device *dev)
 {
+       struct drm_bridge *bridge;
+       struct drm_bridge_helper_funcs *bridge_funcs;
        struct drm_encoder *encoder;
        struct drm_connector *connector;
        struct drm_crtc *crtc;
@@ -250,6 +275,18 @@ void drm_helper_disable_unused_functions(struct drm_device *dev)
                        connector->encoder = NULL;
        }
 
+       list_for_each_entry(bridge, &dev->mode_config.bridge_list, head) {
+               if (!drm_helper_bridge_in_use(bridge)) {
+                       bridge_funcs = bridge->helper_private;
+
+                       if (bridge_funcs->dpms)
+                               bridge_funcs->dpms(bridge, DRM_MODE_DPMS_OFF);
+
+                       /* disconnect crtc from the bridge */
+                       bridge->crtc = NULL;
+               }
+       }
+
        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
                if (!drm_helper_encoder_in_use(encoder)) {
                        drm_encoder_disable(encoder);
@@ -349,6 +386,8 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
        struct drm_display_mode *adjusted_mode, saved_mode, saved_hwmode;
        struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
        struct drm_encoder_helper_funcs *encoder_funcs;
+       struct drm_bridge *bridge;
+       struct drm_bridge_helper_funcs *bridge_funcs;
        int saved_x, saved_y;
        struct drm_encoder *encoder;
        bool ret = true;
@@ -395,6 +434,15 @@ bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
        }
        DRM_DEBUG_KMS("[CRTC:%d]\n", crtc->base.id);
 
+       list_for_each_entry(bridge, &dev->mode_config.bridge_list, head) {
+               if (bridge->crtc != crtc)
+                       continue;
+
+               bridge_funcs = bridge->helper_private;
+               if (bridge_funcs->prepare)
+                       bridge_funcs->prepare(bridge);
+       }
+
        /* Prepare the encoders and CRTCs before setting the mode. */
        list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
 
@@ -511,6 +559,7 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
        struct drm_device *dev;
        struct drm_crtc *save_crtcs, *new_crtc, *crtc;
        struct drm_encoder *save_encoders, *new_encoder, *encoder;
+       struct drm_bridge *new_bridge;
        struct drm_framebuffer *old_fb = NULL;
        bool mode_changed = false; /* if true do a full mode set */
        bool fb_changed = false; /* if true and !mode_changed just do a flip */
@@ -623,6 +672,31 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
                mode_changed = true;
        }
 
+       /* try to find a bridge for the connectors in the connector list */
+       list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
+               struct drm_connector_helper_funcs *connector_funcs =
+                       connector->helper_private;
+
+               new_bridge = connector->bridge;
+               for (ro = 0; ro < set->num_connectors; ro++) {
+                       if (set->connectors[ro] != connector)
+                               continue;
+
+                       if (connector_funcs->best_bridge)
+                               new_bridge = connector_funcs->best_bridge(
+                                               connector);
+                       break;
+               }
+
+               if (new_bridge != connector->bridge) {
+                       DRM_DEBUG_KMS("bridge changed, full mode switch\n");
+                       mode_changed = true;
+                       if (connector->bridge)
+                               connector->bridge->crtc = NULL;
+                       connector->bridge = new_bridge;
+               }
+       }
+
        /* a) traverse passed in connector list and get encoders for them */
        count = 0;
        list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
@@ -684,6 +758,11 @@ int drm_crtc_helper_set_config(struct drm_mode_set *set)
                        mode_changed = true;
                        connector->encoder->crtc = new_crtc;
                }
+               if (connector->bridge && new_crtc != connector->bridge->crtc) {
+                       DRM_DEBUG_KMS("crtc changed, full mode switch\n");
+                       mode_changed = true;
+                       connector->bridge->crtc = new_crtc;
+               }
                if (new_crtc) {
                        DRM_DEBUG_KMS("[CONNECTOR:%d:%s] to [CRTC:%d]\n",
                                connector->base.id, drm_get_connector_name(connector),
@@ -810,6 +889,8 @@ static int drm_helper_choose_crtc_dpms(struct drm_crtc *crtc)
 void drm_helper_connector_dpms(struct drm_connector *connector, int mode)
 {
        struct drm_encoder *encoder = connector->encoder;
+       struct drm_bridge *bridge = connector->bridge;
+
        struct drm_crtc *crtc = encoder ? encoder->crtc : NULL;
        int old_dpms;
 
@@ -827,6 +908,13 @@ void drm_helper_connector_dpms(struct drm_connector *connector, int mode)
                                (*crtc_funcs->dpms) (crtc,
                                                     drm_helper_choose_crtc_dpms(crtc));
                }
+               if (bridge) {
+                       struct drm_bridge_helper_funcs *bridge_funcs =
+                               bridge->helper_private;
+
+                       if (bridge_funcs->dpms)
+                               (*bridge_funcs->dpms)(bridge, mode);
+               }
                if (encoder) {
                        struct drm_encoder_helper_funcs *encoder_funcs = encoder->helper_private;
                        if (encoder_funcs->dpms)
@@ -843,6 +931,13 @@ void drm_helper_connector_dpms(struct drm_connector *connector, int mode)
                                (*encoder_funcs->dpms) (encoder,
                                                        drm_helper_choose_encoder_dpms(encoder));
                }
+               if (bridge) {
+                       struct drm_bridge_helper_funcs *bridge_funcs =
+                               bridge->helper_private;
+
+                       if (bridge_funcs->dpms)
+                               (*bridge_funcs->dpms)(bridge, mode);
+               }
                if (crtc) {
                        struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
                        if (crtc_funcs->dpms)
index f1a0764..b8e26c4 100644 (file)
@@ -46,6 +46,7 @@ struct drm_framebuffer;
 #define DRM_MODE_OBJECT_FB 0xfbfbfbfb
 #define DRM_MODE_OBJECT_BLOB 0xbbbbbbbb
 #define DRM_MODE_OBJECT_PLANE 0xeeeeeeee
+#define DRM_MODE_OBJECT_BRIDGE 0xbdbdbdbd
 
 struct drm_mode_object {
        uint32_t id;
@@ -282,6 +283,7 @@ struct drm_connector;
 struct drm_encoder;
 struct drm_pending_vblank_event;
 struct drm_plane;
+struct drm_bridge;
 
 /**
  * drm_crtc_funcs - control CRTCs for a given device
@@ -528,6 +530,7 @@ enum drm_connector_force {
  * @force: a %DRM_FORCE_<foo> state for forced mode sets
  * @encoder_ids: valid encoders for this connector
  * @encoder: encoder driving this connector, if any
+ * @bridge: bridge connected to this connector
  * @eld: EDID-like data, if present
  * @dvi_dual: dual link DVI, if found
  * @max_tmds_clock: max clock rate, if found
@@ -579,6 +582,7 @@ struct drm_connector {
        enum drm_connector_force force;
        uint32_t encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
        struct drm_encoder *encoder; /* currently active encoder */
+       struct drm_bridge *bridge; /* currently active bridge */
 
        /* EDID bits */
        uint8_t eld[MAX_ELD_BYTES];
@@ -646,6 +650,38 @@ struct drm_plane {
        void *helper_private;
 };
 
+/**
+ * drm_bridge_funcs - drm_bridge control functions
+ * @destroy: make object go away
+ */
+struct drm_bridge_funcs {
+       void (*destroy)(struct drm_bridge *bridge);
+};
+
+/**
+ * drm_bridge - central DRM bridge control structure
+ * @dev: DRM device this bridge belongs to
+ * @head: list management
+ * @base: base mode object
+ * @crtc: crtc we're currently attached to
+ * @connector_type: the type of connector this bridge can attach to
+ * @funcs: control functions
+ * @helper_private: mid-layer private data
+ */
+struct drm_bridge {
+       struct drm_device *dev;
+       struct list_head head;
+
+       struct drm_mode_object base;
+
+       struct drm_crtc *crtc;
+
+       int connector_type;
+
+       const struct drm_bridge_funcs *funcs;
+       void *helper_private;
+};
+
 /**
  * drm_mode_set - new values for a CRTC config change
  * @head: list management
@@ -708,6 +744,7 @@ struct drm_mode_group {
        uint32_t num_crtcs;
        uint32_t num_encoders;
        uint32_t num_connectors;
+       uint32_t num_bridges;
 
        /* list of object IDs for this group */
        uint32_t *id_list;
@@ -722,6 +759,8 @@ struct drm_mode_group {
  * @fb_list: list of framebuffers available
  * @num_connector: number of connectors on this device
  * @connector_list: list of connector objects
+ * @num_bridge: number of bridges on this device
+ * @bridge_list: list of bridge objects
  * @num_encoder: number of encoders on this device
  * @encoder_list: list of encoder objects
  * @num_crtc: number of CRTCs on this device
@@ -749,6 +788,8 @@ struct drm_mode_config {
        struct list_head fb_list;
        int num_connector;
        struct list_head connector_list;
+       int num_bridge;
+       struct list_head bridge_list;
        int num_encoder;
        struct list_head encoder_list;
        int num_plane;
@@ -829,6 +870,10 @@ extern void drm_connector_cleanup(struct drm_connector *connector);
 /* helper to unplug all connectors from sysfs for device */
 extern void drm_connector_unplug_all(struct drm_device *dev);
 
+extern int drm_bridge_init(struct drm_device *dev, struct drm_bridge *bridge,
+                          const struct drm_bridge_funcs *funcs);
+extern void drm_bridge_cleanup(struct drm_bridge *bridge);
+
 extern int drm_encoder_init(struct drm_device *dev,
                            struct drm_encoder *encoder,
                            const struct drm_encoder_funcs *funcs,
index 37515d1..4d89b89 100644 (file)
@@ -102,6 +102,12 @@ struct drm_connector_helper_funcs {
        int (*mode_valid)(struct drm_connector *connector,
                          struct drm_display_mode *mode);
        struct drm_encoder *(*best_encoder)(struct drm_connector *connector);
+       struct drm_bridge *(*best_bridge)(struct drm_connector *connector);
+};
+
+struct drm_bridge_helper_funcs {
+       void (*dpms)(struct drm_bridge *bridge, int mode);
+       void (*prepare)(struct drm_bridge *bridge);
 };
 
 extern int drm_helper_probe_single_connector_modes(struct drm_connector *connector, uint32_t maxX, uint32_t maxY);
@@ -113,6 +119,7 @@ extern bool drm_crtc_helper_set_mode(struct drm_crtc *crtc,
                                     struct drm_framebuffer *old_fb);
 extern bool drm_helper_crtc_in_use(struct drm_crtc *crtc);
 extern bool drm_helper_encoder_in_use(struct drm_encoder *encoder);
+extern bool drm_helper_bridge_in_use(struct drm_bridge *bridge);
 
 extern void drm_helper_connector_dpms(struct drm_connector *connector, int mode);
 
@@ -137,6 +144,12 @@ static inline void drm_connector_helper_add(struct drm_connector *connector,
        connector->helper_private = (void *)funcs;
 }
 
+static inline void drm_bridge_helper_add(struct drm_bridge *bridge,
+               const struct drm_bridge_helper_funcs *funcs)
+{
+       bridge->helper_private = (void *)funcs;
+}
+
 extern int drm_helper_resume_force_mode(struct drm_device *dev);
 extern void drm_kms_helper_poll_init(struct drm_device *dev);
 extern void drm_kms_helper_poll_fini(struct drm_device *dev);