drm/connector: store tile information from displayid (v3)
authorDave Airlie <airlied@redhat.com>
Mon, 20 Oct 2014 06:29:33 +0000 (16:29 +1000)
committerDave Airlie <airlied@redhat.com>
Mon, 8 Dec 2014 23:56:48 +0000 (09:56 +1000)
This creates a tile group from DisplayID block, and
stores the pieces of parsed info from the DisplayID block
into the connector.

v2: add missing signoff, add new connector bits to docs.

v3: remove some debugging.

Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/drm_crtc.c
drivers/gpu/drm/drm_edid.c
include/drm/drm_crtc.h

index 0d1eaa9966e9ea5d920c3080e9665ed6507c17b7..eb89327fb737749fde36dd7cabe273438b752004 100644 (file)
@@ -908,6 +908,11 @@ void drm_connector_cleanup(struct drm_connector *connector)
        struct drm_device *dev = connector->dev;
        struct drm_display_mode *mode, *t;
 
+       if (connector->tile_group) {
+               drm_mode_put_tile_group(dev, connector->tile_group);
+               connector->tile_group = NULL;
+       }
+
        list_for_each_entry_safe(mode, t, &connector->probed_modes, head)
                drm_mode_remove(connector, mode);
 
index a7b5a71856a72a3956cc4f3c6fbe90442e1a535f..72fd8820e186278d8a6bfcf3ff76a2f22999aacb 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/module.h>
 #include <drm/drmP.h>
 #include <drm/drm_edid.h>
+#include <drm/drm_displayid.h>
 
 #define version_greater(edid, maj, min) \
        (((edid)->version > (maj)) || \
@@ -1014,6 +1015,8 @@ module_param_named(edid_fixup, edid_fixup, int, 0400);
 MODULE_PARM_DESC(edid_fixup,
                 "Minimum number of valid EDID header bytes (0-8, default 6)");
 
+static void drm_get_displayid(struct drm_connector *connector,
+                             struct edid *edid);
 /**
  * drm_edid_block_valid - Sanity check the EDID block (base or extension)
  * @raw_edid: pointer to raw EDID block
@@ -1308,10 +1311,15 @@ EXPORT_SYMBOL(drm_probe_ddc);
 struct edid *drm_get_edid(struct drm_connector *connector,
                          struct i2c_adapter *adapter)
 {
+       struct edid *edid;
+
        if (!drm_probe_ddc(adapter))
                return NULL;
 
-       return drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
+       edid = drm_do_get_edid(connector, drm_do_probe_ddc_edid, adapter);
+       if (edid)
+               drm_get_displayid(connector, edid);
+       return edid;
 }
 EXPORT_SYMBOL(drm_get_edid);
 
@@ -2406,7 +2414,7 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid,
 /*
  * Search EDID for CEA extension block.
  */
-static u8 *drm_find_cea_extension(struct edid *edid)
+static u8 *drm_find_edid_extension(struct edid *edid, int ext_id)
 {
        u8 *edid_ext = NULL;
        int i;
@@ -2418,7 +2426,7 @@ static u8 *drm_find_cea_extension(struct edid *edid)
        /* Find CEA extension */
        for (i = 0; i < edid->extensions; i++) {
                edid_ext = (u8 *)edid + EDID_LENGTH * (i + 1);
-               if (edid_ext[0] == CEA_EXT)
+               if (edid_ext[0] == ext_id)
                        break;
        }
 
@@ -2428,6 +2436,16 @@ static u8 *drm_find_cea_extension(struct edid *edid)
        return edid_ext;
 }
 
+static u8 *drm_find_cea_extension(struct edid *edid)
+{
+       return drm_find_edid_extension(edid, CEA_EXT);
+}
+
+static u8 *drm_find_displayid_extension(struct edid *edid)
+{
+       return drm_find_edid_extension(edid, DISPLAYID_EXT);
+}
+
 /*
  * Calculate the alternate clock for the CEA mode
  * (60Hz vs. 59.94Hz etc.)
@@ -3888,3 +3906,123 @@ drm_hdmi_vendor_infoframe_from_display_mode(struct hdmi_vendor_infoframe *frame,
        return 0;
 }
 EXPORT_SYMBOL(drm_hdmi_vendor_infoframe_from_display_mode);
+
+static int drm_parse_display_id(struct drm_connector *connector,
+                               u8 *displayid, int length,
+                               bool is_edid_extension)
+{
+       /* if this is an EDID extension the first byte will be 0x70 */
+       int idx = 0;
+       struct displayid_hdr *base;
+       struct displayid_block *block;
+       u8 csum = 0;
+       int i;
+
+       if (is_edid_extension)
+               idx = 1;
+
+       base = (struct displayid_hdr *)&displayid[idx];
+
+       DRM_DEBUG_KMS("base revision 0x%x, length %d, %d %d\n",
+                     base->rev, base->bytes, base->prod_id, base->ext_count);
+
+       if (base->bytes + 5 > length - idx)
+               return -EINVAL;
+
+       for (i = idx; i <= base->bytes + 5; i++) {
+               csum += displayid[i];
+       }
+       if (csum) {
+               DRM_ERROR("DisplayID checksum invalid, remainder is %d\n", csum);
+               return -EINVAL;
+       }
+
+       block = (struct displayid_block *)&displayid[idx + 4];
+       DRM_DEBUG_KMS("block id %d, rev %d, len %d\n",
+                     block->tag, block->rev, block->num_bytes);
+
+       switch (block->tag) {
+       case DATA_BLOCK_TILED_DISPLAY: {
+               struct displayid_tiled_block *tile = (struct displayid_tiled_block *)block;
+
+               u16 w, h;
+               u8 tile_v_loc, tile_h_loc;
+               u8 num_v_tile, num_h_tile;
+               struct drm_tile_group *tg;
+
+               w = tile->tile_size[0] | tile->tile_size[1] << 8;
+               h = tile->tile_size[2] | tile->tile_size[3] << 8;
+
+               num_v_tile = (tile->topo[0] & 0xf) | (tile->topo[2] & 0x30);
+               num_h_tile = (tile->topo[0] >> 4) | ((tile->topo[2] >> 2) & 0x30);
+               tile_v_loc = (tile->topo[1] & 0xf) | ((tile->topo[2] & 0x3) << 4);
+               tile_h_loc = (tile->topo[1] >> 4) | (((tile->topo[2] >> 2) & 0x3) << 4);
+
+               connector->has_tile = true;
+               if (tile->tile_cap & 0x80)
+                       connector->tile_is_single_monitor = true;
+
+               connector->num_h_tile = num_h_tile + 1;
+               connector->num_v_tile = num_v_tile + 1;
+               connector->tile_h_loc = tile_h_loc;
+               connector->tile_v_loc = tile_v_loc;
+               connector->tile_h_size = w + 1;
+               connector->tile_v_size = h + 1;
+
+               DRM_DEBUG_KMS("tile cap 0x%x\n", tile->tile_cap);
+               DRM_DEBUG_KMS("tile_size %d x %d\n", w + 1, h + 1);
+               DRM_DEBUG_KMS("topo num tiles %dx%d, location %dx%d\n",
+                      num_h_tile + 1, num_v_tile + 1, tile_h_loc, tile_v_loc);
+               DRM_DEBUG_KMS("vend %c%c%c\n", tile->topology_id[0], tile->topology_id[1], tile->topology_id[2]);
+
+               tg = drm_mode_get_tile_group(connector->dev, tile->topology_id);
+               if (!tg) {
+                       tg = drm_mode_create_tile_group(connector->dev, tile->topology_id);
+               }
+               if (!tg)
+                       return -ENOMEM;
+
+               if (connector->tile_group != tg) {
+                       /* if we haven't got a pointer,
+                          take the reference, drop ref to old tile group */
+                       if (connector->tile_group) {
+                               drm_mode_put_tile_group(connector->dev, connector->tile_group);
+                       }
+                       connector->tile_group = tg;
+               } else
+                       /* if same tile group, then release the ref we just took. */
+                       drm_mode_put_tile_group(connector->dev, tg);
+       }
+               break;
+       default:
+               printk("unknown displayid tag %d\n", block->tag);
+               break;
+       }
+       return 0;
+}
+
+static void drm_get_displayid(struct drm_connector *connector,
+                             struct edid *edid)
+{
+       void *displayid = NULL;
+       int ret;
+       connector->has_tile = false;
+       displayid = drm_find_displayid_extension(edid);
+       if (!displayid) {
+               /* drop reference to any tile group we had */
+               goto out_drop_ref;
+       }
+
+       ret = drm_parse_display_id(connector, displayid, EDID_LENGTH, true);
+       if (ret < 0)
+               goto out_drop_ref;
+       if (!connector->has_tile)
+               goto out_drop_ref;
+       return;
+out_drop_ref:
+       if (connector->tile_group) {
+               drm_mode_put_tile_group(connector->dev, connector->tile_group);
+               connector->tile_group = NULL;
+       }
+       return;
+}
index 8f760a2373f91ec46ca6f1ddddffff23870b2d75..01744ed792501278b232691d303225b31fc837e6 100644 (file)
@@ -607,6 +607,15 @@ struct drm_encoder {
  * @bad_edid_counter: track sinks that give us an EDID with invalid checksum
  * @debugfs_entry: debugfs directory for this connector
  * @state: current atomic state for this connector
+ * @has_tile: is this connector connected to a tiled monitor
+ * @tile_group: tile group for the connected monitor
+ * @tile_is_single_monitor: whether the tile is one monitor housing
+ * @num_h_tile: number of horizontal tiles in the tile group
+ * @num_v_tile: number of vertical tiles in the tile group
+ * @tile_h_loc: horizontal location of this tile
+ * @tile_v_loc: vertical location of this tile
+ * @tile_h_size: horizontal size of this tile.
+ * @tile_v_size: vertical size of this tile.
  *
  * Each connector may be connected to one or more CRTCs, or may be clonable by
  * another connector if they can share a CRTC.  Each connector also has a specific
@@ -669,6 +678,15 @@ struct drm_connector {
        struct dentry *debugfs_entry;
 
        struct drm_connector_state *state;
+
+       /* DisplayID bits */
+       bool has_tile;
+       struct drm_tile_group *tile_group;
+       bool tile_is_single_monitor;
+
+       uint8_t num_h_tile, num_v_tile;
+       uint8_t tile_h_loc, tile_v_loc;
+       uint16_t tile_h_size, tile_v_size;
 };
 
 /**